<?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>Case-Study on Tarragon</title><link>https://tarrragon.github.io/blog/tags/case-study/</link><description>Recent content in Case-Study on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 23 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/case-study/index.xml" rel="self" type="application/rss+xml"/><item><title>T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽</title><link>https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/</guid><description>&lt;p>這個案例的核心責任是說明 mock 的「API 層級模擬」和真實服務的「協議層級行為」之間的結構性斷裂。WebSocket 的 text frame（opcode 0x1）和 binary frame（opcode 0x2）在 Dart API 層面都是 &lt;code>sink.add(dynamic)&lt;/code>，但在協議層是不同的 opcode，ttyd 只接受 text frame。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel Flutter app 連接 ttyd WebSocket 終端機。&lt;code>ConnectionManager.sendData()&lt;/code> 接收 &lt;code>Uint8List&lt;/code> 型別的鍵盤輸入，直接傳給 &lt;code>_channel!.sink.add(data)&lt;/code>。Dart 的 &lt;code>IOWebSocketChannel&lt;/code> 對 &lt;code>Uint8List&lt;/code> 發送 binary frame（opcode 0x2），ttyd 期望 text frame（opcode 0x1），收到 binary frame 靜默忽略。&lt;/p>
&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>Unit test 結果&lt;/td>
 &lt;td>192 個全過（&lt;code>FakeWebSocketChannel.sink.add&lt;/code> 不區分型別）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實機表現&lt;/td>
 &lt;td>連線成功但終端機完全無反應&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復&lt;/td>
 &lt;td>&lt;code>if (data is Uint8List) sink.add(String.fromCharCodes(data))&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Mock 模擬的是 Dart API 契約，不是 WebSocket 協議契約&lt;/strong>。&lt;code>FakeWebSocketChannel&lt;/code> 忠實實作了 &lt;code>WebSocketChannel&lt;/code> 的 Dart interface — &lt;code>sink.add(dynamic)&lt;/code> 接受任何型別。但 &lt;code>IOWebSocketChannel&lt;/code> 的 &lt;code>sink.add&lt;/code> 實際行為是：&lt;code>String&lt;/code> → text frame，&lt;code>List&amp;lt;int&amp;gt;&lt;/code> / &lt;code>Uint8List&lt;/code> → binary frame。Mock 沒有也不應該模擬這個協議層行為。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ttyd 的靜默忽略放大了問題&lt;/strong>。如果 ttyd 對 binary frame 回傳錯誤碼或斷線，app 至少會進入 error 狀態讓開發者察覺。靜默忽略讓問題從「連線失敗」變成「連線成功但無回應」，debug 方向完全錯誤。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>型別系統幫不上忙&lt;/strong>。Dart 的 &lt;code>WebSocketSink.add&lt;/code> 簽名是 &lt;code>void add(dynamic event)&lt;/code> — &lt;code>dynamic&lt;/code> 吃掉了型別資訊。即使用強型別語言，如果 API 設計成 &lt;code>dynamic&lt;/code>，型別檢查無法區分協議語意。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Protocol integration test&lt;/strong>：對真實 ttyd 發送 &lt;code>Uint8List&lt;/code> 和 &lt;code>String&lt;/code>，斷言兩者行為差異。一個 5 行 test 就能抓到這個問題。&lt;/li>
&lt;li>&lt;strong>在 sendData 層做型別轉換&lt;/strong>：不依賴下游 channel 的行為，在自己的 API 邊界確保型別正確。&lt;/li>
&lt;li>&lt;strong>Log 送出的 frame type&lt;/strong>：&lt;code>developer.log('WS send: type=${data.runtimeType}')&lt;/code> 讓 debug 時立即可見。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想寫 protocol integration test → &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試&lt;/a>&lt;/li>
&lt;li>想理解 mock 遮蔽的系統性機制 → &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/mock-masking-mechanism/" data-link-title="Mock 遮蔽機制分析" data-link-desc="Mock 在 API 層、協議層、環境層之間製造的結構性盲區 — 斷裂點在哪、為什麼 mock 無法也不應該模擬協議行為">Mock 遮蔽機制分析&lt;/a>&lt;/li>
&lt;li>類似案例（auth handshake） → &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 Auth handshake 缺失&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 mock 的「API 層級模擬」和真實服務的「協議層級行為」之間的結構性斷裂。WebSocket 的 text frame（opcode 0x1）和 binary frame（opcode 0x2）在 Dart API 層面都是 <code>sink.add(dynamic)</code>，但在協議層是不同的 opcode，ttyd 只接受 text frame。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel Flutter app 連接 ttyd WebSocket 終端機。<code>ConnectionManager.sendData()</code> 接收 <code>Uint8List</code> 型別的鍵盤輸入，直接傳給 <code>_channel!.sink.add(data)</code>。Dart 的 <code>IOWebSocketChannel</code> 對 <code>Uint8List</code> 發送 binary frame（opcode 0x2），ttyd 期望 text frame（opcode 0x1），收到 binary frame 靜默忽略。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>影響範圍</td>
          <td>所有鍵盤輸入無效（使用者打字無回應）</td>
      </tr>
      <tr>
          <td>Unit test 結果</td>
          <td>192 個全過（<code>FakeWebSocketChannel.sink.add</code> 不區分型別）</td>
      </tr>
      <tr>
          <td>實機表現</td>
          <td>連線成功但終端機完全無反應</td>
      </tr>
      <tr>
          <td>修復</td>
          <td><code>if (data is Uint8List) sink.add(String.fromCharCodes(data))</code></td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>Mock 模擬的是 Dart API 契約，不是 WebSocket 協議契約</strong>。<code>FakeWebSocketChannel</code> 忠實實作了 <code>WebSocketChannel</code> 的 Dart interface — <code>sink.add(dynamic)</code> 接受任何型別。但 <code>IOWebSocketChannel</code> 的 <code>sink.add</code> 實際行為是：<code>String</code> → text frame，<code>List&lt;int&gt;</code> / <code>Uint8List</code> → binary frame。Mock 沒有也不應該模擬這個協議層行為。</p>
</li>
<li>
<p><strong>ttyd 的靜默忽略放大了問題</strong>。如果 ttyd 對 binary frame 回傳錯誤碼或斷線，app 至少會進入 error 狀態讓開發者察覺。靜默忽略讓問題從「連線失敗」變成「連線成功但無回應」，debug 方向完全錯誤。</p>
</li>
<li>
<p><strong>型別系統幫不上忙</strong>。Dart 的 <code>WebSocketSink.add</code> 簽名是 <code>void add(dynamic event)</code> — <code>dynamic</code> 吃掉了型別資訊。即使用強型別語言，如果 API 設計成 <code>dynamic</code>，型別檢查無法區分協議語意。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>Protocol integration test</strong>：對真實 ttyd 發送 <code>Uint8List</code> 和 <code>String</code>，斷言兩者行為差異。一個 5 行 test 就能抓到這個問題。</li>
<li><strong>在 sendData 層做型別轉換</strong>：不依賴下游 channel 的行為，在自己的 API 邊界確保型別正確。</li>
<li><strong>Log 送出的 frame type</strong>：<code>developer.log('WS send: type=${data.runtimeType}')</code> 讓 debug 時立即可見。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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>
<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>類似案例（auth handshake） → <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 Auth handshake 缺失</a></li>
</ul>
]]></content:encoded></item><item><title>U.C1 Terminal 畫面五個狀態零個退出路徑</title><link>https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/</guid><description>&lt;p>這個案例的核心責任是說明「每個畫面每個狀態都需要退出路徑」這個原則為什麼容易在企劃階段被遺漏，以及用什麼工具能系統性地捕捉這類缺口。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 的 Terminal 畫面用一個 &lt;code>TerminalScreenUiState&lt;/code> enum 管理五個狀態。實機測試前，五個狀態的 UI 實作如下：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>狀態&lt;/th>
 &lt;th>顯示&lt;/th>
 &lt;th>可用操作&lt;/th>
 &lt;th>退出路徑&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>idle&lt;/td>
 &lt;td>空白（自動連線）&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>connecting&lt;/td>
 &lt;td>進度指示&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>connected&lt;/td>
 &lt;td>終端機 + 工具列&lt;/td>
 &lt;td>打字、特殊鍵&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>error&lt;/td>
 &lt;td>錯誤訊息 + 重連按鈕&lt;/td>
 &lt;td>重新連線&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>disconnected&lt;/td>
 &lt;td>「連線中斷」+ 重連按鈕&lt;/td>
 &lt;td>重新連線&lt;/td>
 &lt;td>無&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>使用者從首頁點 Connect Terminal 進入後，無論處於哪個狀態都無法返回首頁。唯一退出方式是殺掉 app。&lt;/p>
&lt;p>W2-001 修復後加入 back 按鈕的狀態：error、disconnected、connecting。但 idle 和 connected 仍缺退出路徑。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>企劃文件的「前端引導」欄位只描述顯示，不描述操作和退出&lt;/strong>。操作盤點表的「前端引導」欄位寫了「連線失敗顯示無法連線」— 覆蓋了 error 狀態的顯示，但沒回答「能做什麼」和「怎麼離開」。從 BDD 操作盤點到 UI 實作之間，缺少把「情境」展開成「畫面 × 狀態 × 操作 × 退出」矩陣的步驟。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>開發者假設使用者只走 happy path&lt;/strong>。「connected 後使用者不會想回首頁」是開發者的隱性假設。實際上使用者可能想：切換到配對畫面重新配對、暫時離開終端機做其他事、遇到問題想重新開始。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>error 和 disconnected 有重連按鈕但沒有 back，也是半成品&lt;/strong>。重連失敗時使用者被困在 error → retry → error 的循環裡。加 back 按鈕讓使用者有第二條路。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>畫面狀態矩陣作為設計產物&lt;/strong>：把每個畫面的每個狀態展開成四欄表格（顯示 / 可用操作 / 進入條件 / 退出路徑）。退出路徑欄位為空 = UX 死胡同，10 分鐘能查完所有畫面。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>退出路徑是預設要求&lt;/strong>：每個畫面的每個狀態至少要有一條退出路徑。即使是 connecting 這種過渡狀態，使用者也應該能取消。這跟 iOS HIG 和 Material Design 對 modal 畫面的 dismiss 要求一致。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Widget test 覆蓋退出路徑&lt;/strong>：狀態矩陣直接轉成 test case — 每個狀態找到 back 按鈕、tap、斷言導航到首頁。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想用狀態矩陣設計畫面 → &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣的定義與填寫方法&lt;/a>&lt;/li>
&lt;li>想建 widget test 覆蓋導航 → &lt;a href="https://tarrragon.github.io/blog/testing/04-ui-automation/" data-link-title="模組四：自動化 UI 驗證" data-link-desc="Widget test 的狀態覆蓋策略、Playwright 驗證流程、螢幕狀態 coverage">模組四：自動化 UI 驗證&lt;/a>&lt;/li>
&lt;li>類似案例（Gate fallback）→ &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2 biometricOnly 無 fallback&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「每個畫面每個狀態都需要退出路徑」這個原則為什麼容易在企劃階段被遺漏，以及用什麼工具能系統性地捕捉這類缺口。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 的 Terminal 畫面用一個 <code>TerminalScreenUiState</code> enum 管理五個狀態。實機測試前，五個狀態的 UI 實作如下：</p>
<table>
  <thead>
      <tr>
          <th>狀態</th>
          <th>顯示</th>
          <th>可用操作</th>
          <th>退出路徑</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>idle</td>
          <td>空白（自動連線）</td>
          <td>無</td>
          <td>無</td>
      </tr>
      <tr>
          <td>connecting</td>
          <td>進度指示</td>
          <td>無</td>
          <td>無</td>
      </tr>
      <tr>
          <td>connected</td>
          <td>終端機 + 工具列</td>
          <td>打字、特殊鍵</td>
          <td>無</td>
      </tr>
      <tr>
          <td>error</td>
          <td>錯誤訊息 + 重連按鈕</td>
          <td>重新連線</td>
          <td>無</td>
      </tr>
      <tr>
          <td>disconnected</td>
          <td>「連線中斷」+ 重連按鈕</td>
          <td>重新連線</td>
          <td>無</td>
      </tr>
  </tbody>
</table>
<p>使用者從首頁點 Connect Terminal 進入後，無論處於哪個狀態都無法返回首頁。唯一退出方式是殺掉 app。</p>
<p>W2-001 修復後加入 back 按鈕的狀態：error、disconnected、connecting。但 idle 和 connected 仍缺退出路徑。</p>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>企劃文件的「前端引導」欄位只描述顯示，不描述操作和退出</strong>。操作盤點表的「前端引導」欄位寫了「連線失敗顯示無法連線」— 覆蓋了 error 狀態的顯示，但沒回答「能做什麼」和「怎麼離開」。從 BDD 操作盤點到 UI 實作之間，缺少把「情境」展開成「畫面 × 狀態 × 操作 × 退出」矩陣的步驟。</p>
</li>
<li>
<p><strong>開發者假設使用者只走 happy path</strong>。「connected 後使用者不會想回首頁」是開發者的隱性假設。實際上使用者可能想：切換到配對畫面重新配對、暫時離開終端機做其他事、遇到問題想重新開始。</p>
</li>
<li>
<p><strong>error 和 disconnected 有重連按鈕但沒有 back，也是半成品</strong>。重連失敗時使用者被困在 error → retry → error 的循環裡。加 back 按鈕讓使用者有第二條路。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li>
<p><strong>畫面狀態矩陣作為設計產物</strong>：把每個畫面的每個狀態展開成四欄表格（顯示 / 可用操作 / 進入條件 / 退出路徑）。退出路徑欄位為空 = UX 死胡同，10 分鐘能查完所有畫面。</p>
</li>
<li>
<p><strong>退出路徑是預設要求</strong>：每個畫面的每個狀態至少要有一條退出路徑。即使是 connecting 這種過渡狀態，使用者也應該能取消。這跟 iOS HIG 和 Material Design 對 modal 畫面的 dismiss 要求一致。</p>
</li>
<li>
<p><strong>Widget test 覆蓋退出路徑</strong>：狀態矩陣直接轉成 test case — 每個狀態找到 back 按鈕、tap、斷言導航到首頁。</p>
</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想用狀態矩陣設計畫面 → <a href="/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣的定義與填寫方法</a></li>
<li>想建 widget test 覆蓋導航 → <a href="/blog/testing/04-ui-automation/" data-link-title="模組四：自動化 UI 驗證" data-link-desc="Widget test 的狀態覆蓋策略、Playwright 驗證流程、螢幕狀態 coverage">模組四：自動化 UI 驗證</a></li>
<li>類似案例（Gate fallback）→ <a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2 biometricOnly 無 fallback</a></li>
</ul>
]]></content:encoded></item><item><title>Case Study：customer support agent 從 task decomposition 到 eval</title><link>https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/</guid><description>&lt;p>本案例的責任是把模組四前面所有原理章節串成一個端到端的設計過程、示範&lt;strong>遇到實際 LLM 應用任務時、設計反射動作的順序&lt;/strong>。每段都標出引用哪章原理、讓讀者看到 principle 章節怎麼落到具體工作。&lt;/p>
&lt;p>用作走查的任務：PM 交派「做一個 customer support agent、能處理用戶查詢、必要時自動完成操作（如改地址）。」本案例聚焦「改地址」這個高頻 query type 走完整流程。&lt;/p>
&lt;h2 id="本案例的設計反射">本案例的設計反射&lt;/h2>
&lt;p>整個流程分七階段：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>觀察人類工作流&lt;/strong>：訪談、決定 task decomposition&lt;/li>
&lt;li>&lt;strong>典範定位&lt;/strong>：哪段該 deterministic、哪段該 fuzzy&lt;/li>
&lt;li>&lt;strong>工作流設計&lt;/strong>：每個 step 選對應的 LLM / tool / RAG / HITL 形態&lt;/li>
&lt;li>&lt;strong>協議跟自主度決定&lt;/strong>：是 single agent / multi-call / multi-agent&lt;/li>
&lt;li>&lt;strong>Trace instrumentation&lt;/strong>：哪些資訊要記&lt;/li>
&lt;li>&lt;strong>Eval 設計&lt;/strong>：先選座標、再選工具&lt;/li>
&lt;li>&lt;strong>Iteration loop&lt;/strong>：error analysis → 修哪一層 → 看 metric 收斂&lt;/li>
&lt;/ol>
&lt;p>初次設計 LLM 應用時最常省略階段 1、2、5、6、直接跳到階段 3 開始寫 prompt——這條路會走進「prompt 改了 20 版、無法判讀有沒有變好」的迭代無收斂。本案例強調的是設計反射動作的順序、不是寫 prompt 技巧。&lt;/p>
&lt;h2 id="階段-1觀察人類工作流">階段 1：觀察人類工作流&lt;/h2>
&lt;p>PM 給的任務描述是「處理用戶查詢」、但「查詢」涵蓋的範圍可能很大。第一個反射動作是&lt;strong>坐在客服旁邊觀察兩天&lt;/strong>、不是打開 IDE。&lt;/p>
&lt;p>實際做的事：&lt;/p>
&lt;ul>
&lt;li>統計收到的 query 類型分佈（退款 / 改地址 / 查詢訂單狀態 / 抱怨 / 開放問題各佔多少）。&lt;/li>
&lt;li>看每類 query 的 human resolution 流程（哪幾步、要查哪些系統、要遵守哪些 policy）。&lt;/li>
&lt;li>看哪幾類 query 是 high volume + low complexity（最值得自動化）、哪幾類是 low volume + high complexity（自動化 ROI 差）。&lt;/li>
&lt;li>記下 human 在哪些 step 卡住、哪些 step 反覆需要查同樣資料。&lt;/li>
&lt;/ul>
&lt;p>訪談結束、你得到一張 task decomposition map。本案例假設聚焦在「用戶請求改地址」這個高頻 query type：&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">User: 「我搬家了、訂單編號 #12345、新地址是 ___」
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">1. 解析意圖 + 抽取訊息（訂單編號、新地址）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">2. 查訂單狀態（已出貨？未出貨？已送達？）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3. 查 policy（這個訂單狀態 + user tier 能不能改地址？）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">4. 若可：執行改地址（呼叫物流 / 庫存 API）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">5. 若不可：解釋為什麼、給替代方案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">6. 草擬回覆 email、發出&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>引用原理：這個 decomposition 本身對應 &lt;a href="https://tarrragon.github.io/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering&lt;/a>（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/deterministic-vs-fuzzy/" data-link-title="Deterministic vs Fuzzy engineering" data-link-desc="LLM 軟體 vs 傳統軟體在資料 / 邏輯 / 行為一致性 / 實驗成本四維度的典範差異、決定哪段該包 guardrail">deterministic-vs-fuzzy&lt;/a> 卡）的「先分解任務、再判讀每段該 deterministic 還是 fuzzy」。&lt;/p>
&lt;h2 id="階段-2典範定位">階段 2：典範定位&lt;/h2>
&lt;p>對每個 step 做典範定位（deterministic / fuzzy）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Step&lt;/th>
 &lt;th>典範&lt;/th>
 &lt;th>為什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1. 解析意圖 + 抽取訊息&lt;/td>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>自由文字 input、需要 LLM 理解&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. 查訂單狀態&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>結構化 query（給 order_id、回 status）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. 查 policy&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>規則可窮舉、policy as code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. 執行改地址&lt;/td>
 &lt;td>Deterministic&lt;/td>
 &lt;td>API call、有 schema 跟錯誤碼&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5. 解釋 / 給替代方案&lt;/td>
 &lt;td>Fuzzy&lt;/td>
 &lt;td>要寫人話、要 tailored to 情境&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6. 草擬 email + 發出&lt;/td>
 &lt;td>Fuzzy（草擬）+ Deterministic（發送）&lt;/td>
 &lt;td>寫 email 是 fuzzy、發 API call 是 deterministic&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀的重點是&lt;strong>邊界各歸各位&lt;/strong>：規則跟政策走 code、人話跟意圖解析走 LLM。&lt;/p></description><content:encoded><![CDATA[<p>本案例的責任是把模組四前面所有原理章節串成一個端到端的設計過程、示範<strong>遇到實際 LLM 應用任務時、設計反射動作的順序</strong>。每段都標出引用哪章原理、讓讀者看到 principle 章節怎麼落到具體工作。</p>
<p>用作走查的任務：PM 交派「做一個 customer support agent、能處理用戶查詢、必要時自動完成操作（如改地址）。」本案例聚焦「改地址」這個高頻 query type 走完整流程。</p>
<h2 id="本案例的設計反射">本案例的設計反射</h2>
<p>整個流程分七階段：</p>
<ol>
<li><strong>觀察人類工作流</strong>：訪談、決定 task decomposition</li>
<li><strong>典範定位</strong>：哪段該 deterministic、哪段該 fuzzy</li>
<li><strong>工作流設計</strong>：每個 step 選對應的 LLM / tool / RAG / HITL 形態</li>
<li><strong>協議跟自主度決定</strong>：是 single agent / multi-call / multi-agent</li>
<li><strong>Trace instrumentation</strong>：哪些資訊要記</li>
<li><strong>Eval 設計</strong>：先選座標、再選工具</li>
<li><strong>Iteration loop</strong>：error analysis → 修哪一層 → 看 metric 收斂</li>
</ol>
<p>初次設計 LLM 應用時最常省略階段 1、2、5、6、直接跳到階段 3 開始寫 prompt——這條路會走進「prompt 改了 20 版、無法判讀有沒有變好」的迭代無收斂。本案例強調的是設計反射動作的順序、不是寫 prompt 技巧。</p>
<h2 id="階段-1觀察人類工作流">階段 1：觀察人類工作流</h2>
<p>PM 給的任務描述是「處理用戶查詢」、但「查詢」涵蓋的範圍可能很大。第一個反射動作是<strong>坐在客服旁邊觀察兩天</strong>、不是打開 IDE。</p>
<p>實際做的事：</p>
<ul>
<li>統計收到的 query 類型分佈（退款 / 改地址 / 查詢訂單狀態 / 抱怨 / 開放問題各佔多少）。</li>
<li>看每類 query 的 human resolution 流程（哪幾步、要查哪些系統、要遵守哪些 policy）。</li>
<li>看哪幾類 query 是 high volume + low complexity（最值得自動化）、哪幾類是 low volume + high complexity（自動化 ROI 差）。</li>
<li>記下 human 在哪些 step 卡住、哪些 step 反覆需要查同樣資料。</li>
</ul>
<p>訪談結束、你得到一張 task decomposition map。本案例假設聚焦在「用戶請求改地址」這個高頻 query type：</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">User: 「我搬家了、訂單編號 #12345、新地址是 ___」
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">1. 解析意圖 + 抽取訊息（訂單編號、新地址）
</span></span><span class="line"><span class="ln">4</span><span class="cl">2. 查訂單狀態（已出貨？未出貨？已送達？）
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. 查 policy（這個訂單狀態 + user tier 能不能改地址？）
</span></span><span class="line"><span class="ln">6</span><span class="cl">4. 若可：執行改地址（呼叫物流 / 庫存 API）
</span></span><span class="line"><span class="ln">7</span><span class="cl">5. 若不可：解釋為什麼、給替代方案
</span></span><span class="line"><span class="ln">8</span><span class="cl">6. 草擬回覆 email、發出</span></span></code></pre></div><p>引用原理：這個 decomposition 本身對應 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a>（<a href="/blog/llm/knowledge-cards/deterministic-vs-fuzzy/" data-link-title="Deterministic vs Fuzzy engineering" data-link-desc="LLM 軟體 vs 傳統軟體在資料 / 邏輯 / 行為一致性 / 實驗成本四維度的典範差異、決定哪段該包 guardrail">deterministic-vs-fuzzy</a> 卡）的「先分解任務、再判讀每段該 deterministic 還是 fuzzy」。</p>
<h2 id="階段-2典範定位">階段 2：典範定位</h2>
<p>對每個 step 做典範定位（deterministic / fuzzy）：</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>典範</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 解析意圖 + 抽取訊息</td>
          <td>Fuzzy</td>
          <td>自由文字 input、需要 LLM 理解</td>
      </tr>
      <tr>
          <td>2. 查訂單狀態</td>
          <td>Deterministic</td>
          <td>結構化 query（給 order_id、回 status）</td>
      </tr>
      <tr>
          <td>3. 查 policy</td>
          <td>Deterministic</td>
          <td>規則可窮舉、policy as code</td>
      </tr>
      <tr>
          <td>4. 執行改地址</td>
          <td>Deterministic</td>
          <td>API call、有 schema 跟錯誤碼</td>
      </tr>
      <tr>
          <td>5. 解釋 / 給替代方案</td>
          <td>Fuzzy</td>
          <td>要寫人話、要 tailored to 情境</td>
      </tr>
      <tr>
          <td>6. 草擬 email + 發出</td>
          <td>Fuzzy（草擬）+ Deterministic（發送）</td>
          <td>寫 email 是 fuzzy、發 API call 是 deterministic</td>
      </tr>
  </tbody>
</table>
<p>判讀的重點是<strong>邊界各歸各位</strong>：規則跟政策走 code、人話跟意圖解析走 LLM。</p>
<ul>
<li>Policy check 寫成 code（如「user tier + 訂單狀態 → 能否改地址」是 deterministic 規則）。對應反例：把規則塞進 prompt 讓 LLM 判斷、會偶爾跳過規則或誤判 tier。</li>
<li>「能不能做」這類 yes/no 走規則。對應反例：用 LLM 算判斷、debug 困難且非確定性。</li>
<li>「Helpful 的回覆」走 LLM 寫。對應反例：在 code 內 hard-code 模板、變成僵化的客服機器人腔。</li>
</ul>
<p>最容易混的邊界在 step 6：「草擬 email」是 fuzzy（要寫人話、tailor to 情境）、「發送 email」是 deterministic（呼叫 API、處理錯誤碼）。把這兩件事拆開、草擬可以 retry / 改 prompt 不影響發送邏輯、發送有結構化 error 不被 LLM hallucinate 蓋過。Step 4「執行改地址」也類似：tool call 本身 deterministic、但是否該 call 的判讀回到 step 3 的 policy check。</p>
<p>引用原理：<a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a> 的「哪段該 deterministic / 哪段該 fuzzy」決策框架、特別是反模式「邊界用錯」段。</p>
<h2 id="階段-3工作流設計">階段 3：工作流設計</h2>
<p>對每個 step 選對應的工具：</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>設計選擇</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 解析意圖 + 抽取訊息</td>
          <td>Vanilla LLM call + structured output（output 強制 JSON schema：intent / order_id / new_address）</td>
      </tr>
      <tr>
          <td>2. 查訂單狀態</td>
          <td>Tool call → 內部 order API</td>
      </tr>
      <tr>
          <td>3. 查 policy</td>
          <td>Tool call → policy engine（純 deterministic、不過 LLM）</td>
      </tr>
      <tr>
          <td>4. 執行改地址</td>
          <td>Tool call → logistics API、寫操作前要 pre-act HITL（高風險 + 不可逆）</td>
      </tr>
      <tr>
          <td>5. 解釋 / 給替代方案</td>
          <td>LLM call + few-shot（從 case 庫 retrieve「類似情境怎麼解釋」、配 RAG）</td>
      </tr>
      <tr>
          <td>6. 草擬 email + 發出</td>
          <td>LLM call 寫 email + structured output 含 subject/body、發送透過 email API</td>
      </tr>
  </tbody>
</table>
<p>兩個容易選錯的 step 展開：</p>
<p><strong>Step 1 為何要 structured output、不是純 prompt 解析</strong>：抽取結果要餵 step 2-4 的 deterministic tool、order_id 抽錯就整個流程斷。純 prompt 描述「請輸出 JSON」是弱保證、structured output / constrained decoding 是強保證（見 <a href="/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10 constrained decoding 內部</a>）。Trade-off：強格式可能犧牲表達彈性、但這個 step 不需要彈性、要的是可靠。</p>
<p><strong>Step 5 為何配 RAG 而非純 few-shot</strong>：客服 case 涵蓋多種情境（訂單已出貨 / 已送達 / VIP / 一般 user / 不同國家 policy）、固定 few-shot 範例 cover 不全。RAG 從歷史 case 庫即時 retrieve 最相似的解釋範例、屬於 <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> context 軸的 retrieval-augmented prompting。</p>
<p>引用原理：</p>
<ul>
<li>Step 1 的 structured output → <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a></li>
<li>Step 2-4 的 tool 設計 → <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3 tool use</a></li>
<li>Step 4 的 pre-act HITL → <a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5 人機協作拓樸</a> pre-act 段。對比講座 Workera appeal 是 post-hoc、本案例選 pre-act 是因為改地址不可逆 + 物流影響大、必須在執行前審</li>
<li>Step 5 的 RAG → <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 RAG 原理</a> + <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> context 軸</li>
</ul>
<h2 id="階段-4協議跟自主度決定">階段 4：協議跟自主度決定</h2>
<p>這個工作流的控制流是線性的（1→2→3→4→5→6）、有條件分支（step 3 結果決定走 4 還是 5）、但每步順序固定。判讀：</p>
<p><strong>該用什麼結構</strong>：</p>
<ul>
<li><strong>不適用 Multi-agent</strong>：步驟順序固定、角色差異不大、orchestration overhead 純增。</li>
<li><strong>不適用 Single agent loop（model 自決下一步）</strong>：本案例假設 single-turn / 短多 turn、步驟順序明確、不需要 agent 自決。若 user 互動多輪 + turn 數不固定（如 user 中途補資訊、改主意、追問）、可考慮 agent loop。</li>
<li><strong>採用 Multi-call pipeline + router</strong>：寫成 deterministic pipeline、step 3 後有 router 分流。</li>
</ul>
<p>引用原理：</p>
<ul>
<li><a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8 multi-agent 拓樸</a> 的「先 multi-call、不夠再 multi-agent」反射</li>
<li><a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7 workflow patterns</a> 的 pipeline + router 模式</li>
<li><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4 agent 架構</a> 的「先 single-call、不夠再 agent」反射</li>
</ul>
<p><strong>自主度</strong>：</p>
<ul>
<li>Step 1（parse）、5（解釋）、6（草擬 email）：full auto。</li>
<li>Step 2、3（查訂單、查 policy）：full auto（read-only）。</li>
<li>Step 4（執行改地址）：pre-act HITL（高風險 + 不可逆）、有 diff show、user 可以 reject。</li>
<li>Step 6（發 email）：可選 pre-act HITL（看公司風格、保守版要審 email、激進版自動發）。</li>
</ul>
<h2 id="階段-5trace-instrumentation">階段 5：Trace Instrumentation</h2>
<p>工作流上線前、先設計要記哪些資訊。<strong>Eval 跟 debug 都靠 trace、沒 trace 後面什麼都做不了</strong>。</p>
<p>每個 step 要記：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Input（完整）</td>
          <td>Debug 時要重現</td>
      </tr>
      <tr>
          <td>Output（完整）</td>
          <td>比對預期、做 regression set</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>找 bottleneck</td>
      </tr>
      <tr>
          <td>Token cost</td>
          <td>算成本</td>
      </tr>
      <tr>
          <td>Step name + version</td>
          <td>追蹤是哪個版本的 prompt / tool</td>
      </tr>
      <tr>
          <td>Decision branch</td>
          <td>Step 3 的 router 走哪邊</td>
      </tr>
      <tr>
          <td>Error（若有）</td>
          <td>結構化 error、不是 string</td>
      </tr>
  </tbody>
</table>
<p>整段 trace 要綁同一個 conversation_id、可以後面 join 起來看完整流程。</p>
<p>引用原理：<a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a>。</p>
<h2 id="階段-6eval-設計">階段 6：Eval 設計</h2>
<p>先選座標、再選工具。對本案例的每個 eval 需求、用 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 三軸座標</a> 定位。下面列的 threshold 數字（95%、80%、≥4 等）是 illustrative、實際數字隨產品 baseline、user 容忍度、業務代價而定、不是通用標準。</p>
<h3 id="eval-1step-1-抽取準不準">Eval 1：Step 1 抽取準不準</h3>
<ul>
<li><strong>三軸</strong>：Objective（有 ground truth）+ Component（測單 step）+ Quantitative（accuracy）。</li>
<li><strong>工具</strong>：寫 100 個有標註的 query、跑 step 1、看 extraction accuracy（order_id 對 + new_address 對的比例）。</li>
<li><strong>Threshold</strong>：&lt; 95% 不上線。</li>
</ul>
<h3 id="eval-2step-2-4-tool-call-行為正確">Eval 2：Step 2-4 tool call 行為正確</h3>
<ul>
<li><strong>三軸</strong>：Objective + Component + Quantitative。</li>
<li><strong>工具</strong>：mock API、給 step 2-4 各 50 個 case、看 tool call 參數對不對、返回值處理對不對。</li>
<li><strong>Threshold</strong>：100%（這是 deterministic 行為、不該有錯）。</li>
</ul>
<h3 id="eval-3step-5-解釋品質">Eval 3：Step 5 解釋品質</h3>
<ul>
<li><strong>三軸</strong>：Subjective（沒有單一正解）+ Component + Quantitative。</li>
<li><strong>工具</strong>：LLM-as-judge with rubric（clarity / helpfulness / tone）、scale 1-5、aggregate average。</li>
<li><strong>Threshold</strong>：average ≥ 4、no 1-2 比例 &lt; 5%。</li>
</ul>
<h3 id="eval-4step-6-email-品質">Eval 4：Step 6 email 品質</h3>
<ul>
<li><strong>三軸</strong>：Subjective + Component + Quantitative + 加 Qualitative human review。</li>
<li><strong>工具</strong>：LLM judge 給分 + 每週抽 20 封 human review、看是否有 hallucinate 承諾、是否符合公司 tone。</li>
<li><strong>Threshold</strong>：judge 平均 ≥ 4、human review 沒有 critical issue。</li>
</ul>
<h3 id="eval-5e2e-success-rate">Eval 5：E2E success rate</h3>
<ul>
<li><strong>三軸</strong>：Objective + End-to-end + Quantitative。</li>
<li><strong>工具</strong>：跑 200 個 representative case、看「完整完成 + user 沒申訴」的比例。</li>
<li><strong>Threshold</strong>：≥ 85% baseline、降到 &lt; 80% alert。</li>
</ul>
<h3 id="eval-6user-滿意度">Eval 6：User 滿意度</h3>
<ul>
<li><strong>三軸</strong>：Subjective + End-to-end + Quantitative。</li>
<li><strong>工具</strong>：每次互動結束顯示 thumbs up/down + optional 留言、追蹤 weekly。</li>
<li><strong>Threshold</strong>：thumbs up rate &gt; 80%、appeal rate &lt; 5%。</li>
</ul>
<h3 id="eval-7failure-mode-pattern持續做">Eval 7：Failure mode pattern（持續做）</h3>
<ul>
<li><strong>三軸</strong>：Objective / Subjective + End-to-end + Qualitative。</li>
<li><strong>工具</strong>：每週讀 50 個 sampled traces + 100% 讀 failure / appeal traces、找 emerging pattern。</li>
<li><strong>產出</strong>：bug ticket、prompt 修改 hypothesis、policy 補強 hypothesis。</li>
</ul>
<p>引用原理：</p>
<ul>
<li>三軸座標 → <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 eval design framework</a></li>
<li>LLM judge rubric → <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a></li>
<li>Trace 接 eval → <a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a></li>
</ul>
<h2 id="階段-7iteration-loop">階段 7：Iteration Loop</h2>
<p>上線後、不是「等出問題」、是<strong>持續 iteration</strong>。典型 iteration cycle：</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">Production trace + eval result
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">[Error analysis：找 emerging pattern]
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   Hypothesis：哪一層有問題？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">   ├── Prompt 層 → 改 prompt → A/B test → 看 eval 收斂
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ├── Tool 層   → 改 tool / schema → 跑 component eval → 收斂
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   ├── RAG 層    → 改 chunking / query rewriting → 跑 [retrieval recall](/llm/knowledge-cards/retrieval-recall/) → 收斂
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ├── Policy 層 → 改 deterministic rule → 跑 step 3 component eval → 收斂
</span></span><span class="line"><span class="ln">10</span><span class="cl">   └── Model 層  → 換 model → 跑全 eval set → 收斂
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">12</span><span class="cl">[改動進 production]
</span></span><span class="line"><span class="ln">13</span><span class="cl">   ↓
</span></span><span class="line"><span class="ln">14</span><span class="cl">[Frozen baseline 留著、新版本跟它比、漂移看得見]</span></span></code></pre></div><p>判讀「該改哪一層」的反射：</p>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>該改的層</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Step 1 抽錯訊息</td>
          <td>Prompt / structured output schema</td>
      </tr>
      <tr>
          <td>Tool call 參數錯</td>
          <td>Prompt 內 tool description / few-shot</td>
      </tr>
      <tr>
          <td>Tool 跑掛</td>
          <td>Tool 實作（不是 LLM 問題）</td>
      </tr>
      <tr>
          <td>RAG retrieve 不到相關案例</td>
          <td>Chunking / embedding / query rewriting</td>
      </tr>
      <tr>
          <td>Policy judgment 錯</td>
          <td>Deterministic rule（不是 LLM 問題）</td>
      </tr>
      <tr>
          <td>Email tone 不對</td>
          <td>Prompt（role / few-shot）</td>
      </tr>
      <tr>
          <td>Email hallucinate 承諾</td>
          <td>Output validator（不只是 prompt）</td>
      </tr>
      <tr>
          <td>整體 latency 太高</td>
          <td>找 trace bottleneck、可能要 cache / 並行</td>
      </tr>
  </tbody>
</table>
<p>引用原理：</p>
<ul>
<li>Prompt 跟 model 層的失敗診斷 → <a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 技術光譜</a> systematic vs random error</li>
<li>整體 fuzzy / deterministic 邊界判讀 → <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a></li>
</ul>
<h2 id="五個容易遺漏的設計反射">五個容易遺漏的設計反射</h2>
<p>實務上常常省略這五個反射動作、走進無收斂迭代：</p>
<h3 id="反射一先觀察再開-ide">反射一：先觀察、再開 IDE</h3>
<p>階段 1 的價值是把 task decomposition 跟真實人類工作流對齊。沒這層對齊、寫出來的 prompt 跟 tool 拆法跟 reality 偏離、三天後重做。階段 1 的兩天比階段 3 的兩週值得。對應反例：「我先寫個 prompt 試試」、跳過觀察直接寫 code。</p>
<h3 id="反射二policy-寫成-codellm-只解析意圖">反射二：Policy 寫成 code、LLM 只解析意圖</h3>
<p>判斷類規則（user tier、訂單狀態、可否操作）走 deterministic code、LLM 只負責「user 想做什麼」這層意圖抽取。這條邊界讓 debug 容易、規則更新不用 prompt iteration。對應反例：「LLM、請判斷這個訂單能不能改地址、規則如下：&hellip;」——把判斷塞進 prompt、debug 困難、規則漂移無從追蹤。對應 <a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8</a> 的「邊界用錯」反模式。</p>
<h3 id="反射三trace-是-day-1-設計">反射三：Trace 是 day-1 設計</h3>
<p>從第一天就把 input / output / latency / token / step name / decision branch / error 進 trace、綁同一個 conversation_id。Eval 跟 debug 都靠 trace、沒 trace 後面什麼都做不了。對應反例：「先讓系統跑起來、之後再加 trace」——出 bug 時 debug 從零開始、production trace 不可回溯。</p>
<h3 id="反射四deterministic-行為用-deterministic-check">反射四：Deterministic 行為用 deterministic check</h3>
<p>有 ground truth 的行為（抽取對不對、API 參數對不對、JSON schema 合不合）用 Python 函數驗證、判斷成本低、精度高。LLM judge 留給沒 ground truth 的 subjective 行為。對應反例：用 LLM judge 測「step 1 抽取對不對」——cost 翻倍、精度反而不如 deterministic check。對應 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13</a> 軸誤選一。</p>
<h3 id="反射五保留-frozen-baseline">反射五：保留 frozen baseline</h3>
<p><a href="/blog/llm/knowledge-cards/frozen-baseline/" data-link-title="Frozen baseline" data-link-desc="Eval 系統中固定特定 prompt &#43; model 當長期對照、讓行為漂移可見的標準作法">Frozen baseline</a> 是把某個特定 prompt + 特定 model 跑 production 一段時間後 freeze 起來、每次新版本都跟它比、漂移看得見。對應反例：每次只跟「上一版」比、半年後累積漂移完全不可見、「整體變好了沒」無從回答。</p>
<h2 id="跟其他章節的對應總表">跟其他章節的對應總表</h2>
<p>本案例每階段引用的原理章節彙整：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>引用章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. 觀察人類工作流</td>
          <td><a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a></td>
      </tr>
      <tr>
          <td>2. 典範定位</td>
          <td><a href="/blog/llm/00-foundations/deterministic-vs-fuzzy-engineering/" data-link-title="0.8 Deterministic vs Fuzzy Engineering：軟體設計典範的位移" data-link-desc="傳統 deterministic 軟體跟 fuzzy LLM 軟體在資料、邏輯、分解、實驗成本四個維度的根本差異、以及哪段該 deterministic、哪段該 fuzzy 的決策框架">0.8 fuzzy engineering</a></td>
      </tr>
      <tr>
          <td>3. 工作流設計（prompt / tool / RAG / HITL）</td>
          <td><a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0</a>、<a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1</a>、<a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">4.3</a>、<a href="/blog/llm/04-applications/human-ai-collaboration/" data-link-title="4.5 人機協作拓樸：何時人介入、怎麼介入" data-link-desc="Centaur vs Cyborg 工作模式、jagged frontier、HITL 三種觸發時機（pre-act / mid-stream / post-hoc）、確認流程的設計避免橡皮圖章化">4.5</a></td>
      </tr>
      <tr>
          <td>4. 結構決定（multi-call vs agent vs multi-agent）</td>
          <td><a href="/blog/llm/04-applications/agent-architecture/" data-link-title="4.4 Agent 架構原理" data-link-desc="Agent loop 結構、失敗模式、什麼任務適合 vs 不適合、跟人類審查的協作模型">4.4</a>、<a href="/blog/llm/04-applications/workflow-patterns/" data-link-title="4.7 Workflow 編排模式" data-link-desc="Pipeline / router / parallel / reflection：多 LLM call 組合的四種基本模式與退化條件">4.7</a>、<a href="/blog/llm/04-applications/multi-agent-topology/" data-link-title="4.8 Multi-Agent 拓樸：flat / hierarchical / agent-as-tool" data-link-desc="從 multi-call workflow 走到 multi-agent system 的判讀、flat vs hierarchical 拓樸、agent-as-tool 的 MCP 視角、specialization 跟 orchestration overhead 的取捨">4.8</a></td>
      </tr>
      <tr>
          <td>5. Trace instrumentation</td>
          <td><a href="/blog/llm/04-applications/llm-tracing-and-observability/" data-link-title="4.20 LLM tracing 與 observability" data-link-desc="OpenTelemetry GenAI semantic conventions、結構化 span 設計、cost / latency 監控、failure debug 流程、跟 LLM-as-judge eval 的串接">4.20 LLM tracing</a></td>
      </tr>
      <tr>
          <td>6. Eval 設計</td>
          <td><a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 eval framework</a>、<a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14</a>、<a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21</a></td>
      </tr>
      <tr>
          <td>7. Iteration loop</td>
          <td><a href="/blog/llm/04-applications/prompt-techniques-landscape/" data-link-title="4.0 Prompt 技術光譜：手法分類、取捨、組合模式" data-link-desc="Zero-shot / few-shot、chain-of-thought、role / template、reflection 等 prompt 技術的分類與取捨、何時 stack 何時不要 stack、跟 fine-tune / RAG / chaining 的邊界">4.0 prompt 光譜</a> systematic vs random error 段</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步">下一步</h2>
<p>返回：<a href="/blog/llm/04-applications/" data-link-title="模組四：LLM 應用層原理" data-link-desc="Prompt 技術光譜、RAG、tool use、agent、應用層協議、人機協作、multi-agent、workflow 編排、eval 設計：跨工具不變的概念地圖">模組四首頁</a>、或回到 <a href="/blog/llm/04-applications/hands-on/" data-link-title="4.x Hands-on：端到端案例" data-link-desc="把模組四的所有原理串成具體 case study：從 task decomposition、workflow 設計、eval 設計到 iteration loop">hands-on 索引</a>。</p>
]]></content:encoded></item><item><title>9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/</guid><description>&lt;p>這個案例的核心責任是提供「極端可預期峰值」的容量設計參考點。Prime Day 是 Amazon 每年最大的單一行銷事件、發生時間提前數月公告、所有相依服務都能進入準備階段、是最接近「教科書版本的容量規劃」的真實場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>2025 年 Prime Day 期間 AWS 主要服務的峰值數字（引自 &lt;a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS News Blog&lt;/a>）：&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>Amazon SQS&lt;/td>
 &lt;td>1.66 億訊息 / 秒（新紀錄）&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS Lambda&lt;/td>
 &lt;td>每日 1.7 兆次呼叫&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon API Gateway&lt;/td>
 &lt;td>1 兆次內部請求&lt;/td>
 &lt;td>+30%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;td>1.51 億 RPS、毫秒級回應&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon ElastiCache&lt;/td>
 &lt;td>每日 1.5 quadrillion 請求&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon CloudFront&lt;/td>
 &lt;td>3 兆次 HTTP 請求&lt;/td>
 &lt;td>+43%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon Kinesis Streams&lt;/td>
 &lt;td>8.07 億 records / 秒峰值&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon EBS&lt;/td>
 &lt;td>20.3 兆次 I/O&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon Aurora&lt;/td>
 &lt;td>5000 億次 transaction&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon SageMaker AI&lt;/td>
 &lt;td>6260 億次推論請求&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon ECS on Fargate&lt;/td>
 &lt;td>每日 1840 萬個 task&lt;/td>
 &lt;td>+77%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS FIS（混沌實驗）&lt;/td>
 &lt;td>6800+ 次彈性測試&lt;/td>
 &lt;td>8 倍於 2024&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>基礎設施層面：AWS Graviton 處理器承擔超過 40% 的 EC2 compute、部署超過 87,000 顆 Inferentia / Trainium AI 晶片、AWS Outposts 對機器人下達 5.24 億條指令（年增 160%）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Prime Day 是「可預期極端峰值」的標竿。它的容量問題不是「會不會撐住」、而是「準備到什麼程度才划算」。對應主章問題節點：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Capacity Planning&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6&lt;/a>）：年度活動的容量計算可以用歷史 baseline × 預期成長 × headroom 三項相乘、但 Prime Day 規模下、每一項的不確定性放大都會變成數百萬美金成本差異。Amazon 公開的年增率（API Gateway +30%、CloudFront +43%、ECS on Fargate +77%）顯示連 Amazon 自己每年的成長預測都不能直線外推。&lt;/li>
&lt;li>&lt;strong>Performance Observability&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8&lt;/a>）：DynamoDB 「1.51 億 RPS、毫秒級回應」這種敘述同時包含吞吐與延遲、是 production-grade 容量地圖的最小單位。只說吞吐不說延伸分布、容量資訊不完整。&lt;/li>
&lt;li>&lt;strong>Improvement Loop&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9&lt;/a>）：FIS 混沌實驗 8 倍於 2024 顯示 Amazon 把「在 Prime Day 之前主動製造失敗」當成必修課、不是事後檢討。這層投資跟容量規劃同等重要。&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>這個案例可以抽出三個跨平台可重用的工程做法。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>把可預期峰值寫進服務級 SLO&lt;/strong>：Prime Day 在 SQS / Lambda / DynamoDB / Aurora 都建立了內部 SLO baseline、平日跑在 baseline 之下、峰值是擴張到「設計容量」而不是「實驗容量」。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 直接對齊。&lt;/li>
&lt;li>&lt;strong>pre-scaling + scheduled capacity&lt;/strong>：CloudFront 43%、API Gateway 30% 的年增率都是 &lt;em>提前算進&lt;/em> 容量計畫、不是當天 reactive 擴容。對應 EC2 Auto Scaling 的 &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">predictive / scheduled scaling&lt;/a> 模式。&lt;/li>
&lt;li>&lt;strong>事前主動製造失敗、不靠當天 reactive&lt;/strong>：FIS 8x 成長代表「在 Prime Day 之前 6800 次 chaos test」、把驗證成本前置到容量規劃階段。這條跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing&lt;/a> 形成閉環 — 06 講失敗模式驗證、09 講容量地圖、兩者在 Prime Day 級別的事件上必須一起做。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP 的 Compute Engine MIG + Predictive Autoscaler、Azure 的 VM Scale Sets + Predictive Autoscale、Kubernetes 生態的 KEDA + Karpenter 都可以實作同樣的 pre-scaling 策略。差異是 vendor 整合度、不是工程概念。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「極端可預期峰值」的容量設計參考點。Prime Day 是 Amazon 每年最大的單一行銷事件、發生時間提前數月公告、所有相依服務都能進入準備階段、是最接近「教科書版本的容量規劃」的真實場景。</p>
<h2 id="觀察">觀察</h2>
<p>2025 年 Prime Day 期間 AWS 主要服務的峰值數字（引自 <a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS News Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>峰值</th>
          <th>年增率</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Amazon SQS</td>
          <td>1.66 億訊息 / 秒（新紀錄）</td>
          <td>-</td>
      </tr>
      <tr>
          <td>AWS Lambda</td>
          <td>每日 1.7 兆次呼叫</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon API Gateway</td>
          <td>1 兆次內部請求</td>
          <td>+30%</td>
      </tr>
      <tr>
          <td>Amazon DynamoDB</td>
          <td>1.51 億 RPS、毫秒級回應</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon ElastiCache</td>
          <td>每日 1.5 quadrillion 請求</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon CloudFront</td>
          <td>3 兆次 HTTP 請求</td>
          <td>+43%</td>
      </tr>
      <tr>
          <td>Amazon Kinesis Streams</td>
          <td>8.07 億 records / 秒峰值</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon EBS</td>
          <td>20.3 兆次 I/O</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon Aurora</td>
          <td>5000 億次 transaction</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon SageMaker AI</td>
          <td>6260 億次推論請求</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon ECS on Fargate</td>
          <td>每日 1840 萬個 task</td>
          <td>+77%</td>
      </tr>
      <tr>
          <td>AWS FIS（混沌實驗）</td>
          <td>6800+ 次彈性測試</td>
          <td>8 倍於 2024</td>
      </tr>
  </tbody>
</table>
<p>基礎設施層面：AWS Graviton 處理器承擔超過 40% 的 EC2 compute、部署超過 87,000 顆 Inferentia / Trainium AI 晶片、AWS Outposts 對機器人下達 5.24 億條指令（年增 160%）。</p>
<h2 id="判讀">判讀</h2>
<p>Prime Day 是「可預期極端峰值」的標竿。它的容量問題不是「會不會撐住」、而是「準備到什麼程度才划算」。對應主章問題節點：</p>
<ol>
<li><strong>Capacity Planning</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6</a>）：年度活動的容量計算可以用歷史 baseline × 預期成長 × headroom 三項相乘、但 Prime Day 規模下、每一項的不確定性放大都會變成數百萬美金成本差異。Amazon 公開的年增率（API Gateway +30%、CloudFront +43%、ECS on Fargate +77%）顯示連 Amazon 自己每年的成長預測都不能直線外推。</li>
<li><strong>Performance Observability</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8</a>）：DynamoDB 「1.51 億 RPS、毫秒級回應」這種敘述同時包含吞吐與延遲、是 production-grade 容量地圖的最小單位。只說吞吐不說延伸分布、容量資訊不完整。</li>
<li><strong>Improvement Loop</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9</a>）：FIS 混沌實驗 8 倍於 2024 顯示 Amazon 把「在 Prime Day 之前主動製造失敗」當成必修課、不是事後檢討。這層投資跟容量規劃同等重要。</li>
</ol>
<h2 id="策略">策略</h2>
<p>這個案例可以抽出三個跨平台可重用的工程做法。</p>
<ol>
<li><strong>把可預期峰值寫進服務級 SLO</strong>：Prime Day 在 SQS / Lambda / DynamoDB / Aurora 都建立了內部 SLO baseline、平日跑在 baseline 之下、峰值是擴張到「設計容量」而不是「實驗容量」。這跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 直接對齊。</li>
<li><strong>pre-scaling + scheduled capacity</strong>：CloudFront 43%、API Gateway 30% 的年增率都是 <em>提前算進</em> 容量計畫、不是當天 reactive 擴容。對應 EC2 Auto Scaling 的 <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">predictive / scheduled scaling</a> 模式。</li>
<li><strong>事前主動製造失敗、不靠當天 reactive</strong>：FIS 8x 成長代表「在 Prime Day 之前 6800 次 chaos test」、把驗證成本前置到容量規劃階段。這條跟 <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing</a> 形成閉環 — 06 講失敗模式驗證、09 講容量地圖、兩者在 Prime Day 級別的事件上必須一起做。</li>
</ol>
<p>跨平台等效：GCP 的 Compute Engine MIG + Predictive Autoscaler、Azure 的 VM Scale Sets + Predictive Autoscale、Kubernetes 生態的 KEDA + Karpenter 都可以實作同樣的 pre-scaling 策略。差異是 vendor 整合度、不是工程概念。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃年度活動容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>想設計可預期峰值的 SLO → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget 政策</a></li>
<li>想做事前混沌驗證 → <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing</a> + <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">06.22 Steady State Definition</a></li>
<li>對照不同形狀的峰值 → <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a>（事件型不可預期峰值）/ <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a>（無峰值低延遲）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS services scale to new heights for Prime Day 2025: key metrics and milestones</a></li>
<li><a href="https://aws.amazon.com/blogs/industries/conquering-peak-retail-events-with-aws/">Conquering Peak Retail Events with AWS</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">Predictive scaling for Amazon EC2 Auto Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>2.C1 Meta：Cache Consistency 升級</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cache-consistency-upgrade/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cache-consistency-upgrade/</guid><description>&lt;p>這個案例的核心責任是說明快取轉換不只在容量與速度，還包括一致性治理能力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Meta 指出快取在 promotion、shard move、故障恢復時容易引入不一致，單靠傳統 invalidation 很難在大規模系統維持穩定。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當快取已是核心路徑，資料新鮮度問題會直接變成服務正確性問題。這時候轉換重點是把一致性追蹤與異常定位制度化，改一個 TTL 解決不了結構問題。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先定義 inconsistency 來源點與觀測點。&lt;/li>
&lt;li>將 mutation tracing 納入治理，而不是只看命中率。&lt;/li>
&lt;li>把一致性指標接到告警與回退條件。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3 TTL/eviction&lt;/a>，再接 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 telemetry data quality&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2022/06/08/core-infra/cache-made-consistent/">Cache made consistent&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取轉換不只在容量與速度，還包括一致性治理能力。</p>
<h2 id="觀察">觀察</h2>
<p>Meta 指出快取在 promotion、shard move、故障恢復時容易引入不一致，單靠傳統 invalidation 很難在大規模系統維持穩定。</p>
<h2 id="判讀">判讀</h2>
<p>當快取已是核心路徑，資料新鮮度問題會直接變成服務正確性問題。這時候轉換重點是把一致性追蹤與異常定位制度化，改一個 TTL 解決不了結構問題。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先定義 inconsistency 來源點與觀測點。</li>
<li>將 mutation tracing 納入治理，而不是只看命中率。</li>
<li>把一致性指標接到告警與回退條件。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>先回 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside</a> 與 <a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3 TTL/eviction</a>，再接 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 telemetry data quality</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2022/06/08/core-infra/cache-made-consistent/">Cache made consistent</a></li>
</ul>
]]></content:encoded></item><item><title>3.C1 Meta：FOQS 從區域到全域佇列遷移</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/</guid><description>&lt;p>這個案例的核心責任是說明 queue 轉換不只換 broker，還包含路由與可用性模型重整。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FOQS 從區域安裝轉為全域架構，目標是讓災害期間佇列資料仍可被存取，並控制遷移期間的延遲與可用性風險。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 queue 成為跨區關鍵路徑，轉換焦點是 discoverability、routing freshness 與 tenant 遷移節奏。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立全域路由層，再分批搬遷租戶。&lt;/li>
&lt;li>針對 stale routing 做補貨延遲治理。&lt;/li>
&lt;li>用零停機遷移策略保留客戶端連續性。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2022/01/18/production-engineering/foqs-disaster-ready/">FOQS disaster-ready migration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 queue 轉換不只換 broker，還包含路由與可用性模型重整。</p>
<h2 id="觀察">觀察</h2>
<p>FOQS 從區域安裝轉為全域架構，目標是讓災害期間佇列資料仍可被存取，並控制遷移期間的延遲與可用性風險。</p>
<h2 id="判讀">判讀</h2>
<p>當 queue 成為跨區關鍵路徑，轉換焦點是 discoverability、routing freshness 與 tenant 遷移節奏。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立全域路由層，再分批搬遷租戶。</li>
<li>針對 stale routing 做補貨延遲治理。</li>
<li>用零停機遷移策略保留客戶端連續性。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2022/01/18/production-engineering/foqs-disaster-ready/">FOQS disaster-ready migration</a></li>
</ul>
]]></content:encoded></item><item><title>5.C1 Tradeshift：self-managed Kubernetes 遷移到 EKS</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/</guid><description>&lt;p>這個案例的核心責任是把平台遷移從「搬家」改寫成「流量與依賴分段切換」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Tradeshift 從 self-hosted Kubernetes 遷移到 Amazon EKS，legacy 叢集上運行 409 個 service。遷移以零停機為硬性前提，且要求對應用程式碼零修改——遷移的複雜度由平台層吸收，服務團隊不改程式碼。&lt;/p>
&lt;p>遷移採用 parallel cluster 架構：新舊叢集同時運行，透過 Linkerd service mesh 的 multi-cluster 能力橋接。Linkerd 在新叢集中建立 mirrored service（帶叢集後綴），讓跨叢集服務呼叫對應用層透明。流量切換用 Linkerd 的 traffic splitting policy 分批控制，不需要修改個別服務的路由邏輯。&lt;/p>
&lt;p>跨叢集延遲實測：從 EKS 叢集存取 legacy 叢集的 gateway，P50=2ms、P95=8ms、P99=9ms。這個延遲水平足以支撐遷移期的跨叢集服務呼叫，但對延遲敏感的路徑仍需要在同一叢集內完成切換才能消除這層額外延遲。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這類遷移的難點在跨叢集服務依賴與流量切換，Kubernetes API 相容性反而是最容易處理的部分。Linkerd multi-cluster 在這個案例中解決了三個問題：跨叢集 service discovery（mirrored service 自動同步）、流量分批控制（traffic splitting 不改應用碼）、遷移期 rollback（切回舊叢集只需調整 traffic split 比例）。&lt;/p>
&lt;p>409 個 service 的遷移不是一次完成——service 之間有依賴關係，遷移順序要按依賴拓樸規劃。被多個服務依賴的基礎 service（auth、config）通常最後遷移或在兩邊都保留，避免跨叢集呼叫成為所有服務的共同瓶頸。&lt;/p>
&lt;p>遷移期最大的隱性風險是「跨叢集延遲累積」。單次跨叢集呼叫 P99=9ms 看似可接受，但一條請求路徑如果串接 5 個跨叢集呼叫，累積延遲可達 45ms。遷移規劃要把服務依賴鏈上的跨叢集呼叫次數納入切換順序考量。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>建立 parallel cluster + mesh bridge&lt;/strong>：新叢集用 EKS 建立，Linkerd multi-cluster 連接新舊叢集，mirrored service 讓跨叢集呼叫透明。&lt;/li>
&lt;li>&lt;strong>按依賴拓樸排序遷移批次&lt;/strong>：葉子服務（無下游依賴）先遷，基礎服務最後遷或雙邊保留。每批遷移後驗證跨叢集延遲是否在可接受範圍。&lt;/li>
&lt;li>&lt;strong>Traffic splitting 分批切流量&lt;/strong>：每個服務遷移後，用 traffic split 從 0% 開始逐步把流量導向新叢集。觀察 per-service error rate 與 latency，確認穩定後提高比例。&lt;/li>
&lt;li>&lt;strong>保留 rollback 路徑&lt;/strong>：舊叢集服務不立即下線，traffic split 隨時可切回 100% 舊叢集。rollback 操作是調整 split 比例，不需要重新部署。&lt;/li>
&lt;li>&lt;strong>遷移完成後拆除 mesh bridge&lt;/strong>：所有服務切換完成且穩定觀測後，移除跨叢集 Linkerd 連線，舊叢集下線。&lt;/li>
&lt;/ol>
&lt;h2 id="可回寫的章節段落">可回寫的章節段落&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移&lt;/a>：traffic split 的分批切換與回退策略&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/service-discovery/" data-link-title="5.4 service discovery" data-link-desc="整理 endpoint discovery 與 DNS">5.4 跨叢集 Discovery&lt;/a>：Linkerd mirrored service 是跨叢集 discovery 的 service mesh federation 做法&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate&lt;/a>：每批切換的放行條件與停損訊號&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/containers/tradeshifts-migration-to-amazon-eks-without-downtime-using-linkerd/">Tradeshift migration to EKS without downtime using Linkerd&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把平台遷移從「搬家」改寫成「流量與依賴分段切換」。</p>
<h2 id="觀察">觀察</h2>
<p>Tradeshift 從 self-hosted Kubernetes 遷移到 Amazon EKS，legacy 叢集上運行 409 個 service。遷移以零停機為硬性前提，且要求對應用程式碼零修改——遷移的複雜度由平台層吸收，服務團隊不改程式碼。</p>
<p>遷移採用 parallel cluster 架構：新舊叢集同時運行，透過 Linkerd service mesh 的 multi-cluster 能力橋接。Linkerd 在新叢集中建立 mirrored service（帶叢集後綴），讓跨叢集服務呼叫對應用層透明。流量切換用 Linkerd 的 traffic splitting policy 分批控制，不需要修改個別服務的路由邏輯。</p>
<p>跨叢集延遲實測：從 EKS 叢集存取 legacy 叢集的 gateway，P50=2ms、P95=8ms、P99=9ms。這個延遲水平足以支撐遷移期的跨叢集服務呼叫，但對延遲敏感的路徑仍需要在同一叢集內完成切換才能消除這層額外延遲。</p>
<h2 id="判讀">判讀</h2>
<p>這類遷移的難點在跨叢集服務依賴與流量切換，Kubernetes API 相容性反而是最容易處理的部分。Linkerd multi-cluster 在這個案例中解決了三個問題：跨叢集 service discovery（mirrored service 自動同步）、流量分批控制（traffic splitting 不改應用碼）、遷移期 rollback（切回舊叢集只需調整 traffic split 比例）。</p>
<p>409 個 service 的遷移不是一次完成——service 之間有依賴關係，遷移順序要按依賴拓樸規劃。被多個服務依賴的基礎 service（auth、config）通常最後遷移或在兩邊都保留，避免跨叢集呼叫成為所有服務的共同瓶頸。</p>
<p>遷移期最大的隱性風險是「跨叢集延遲累積」。單次跨叢集呼叫 P99=9ms 看似可接受，但一條請求路徑如果串接 5 個跨叢集呼叫，累積延遲可達 45ms。遷移規劃要把服務依賴鏈上的跨叢集呼叫次數納入切換順序考量。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>建立 parallel cluster + mesh bridge</strong>：新叢集用 EKS 建立，Linkerd multi-cluster 連接新舊叢集，mirrored service 讓跨叢集呼叫透明。</li>
<li><strong>按依賴拓樸排序遷移批次</strong>：葉子服務（無下游依賴）先遷，基礎服務最後遷或雙邊保留。每批遷移後驗證跨叢集延遲是否在可接受範圍。</li>
<li><strong>Traffic splitting 分批切流量</strong>：每個服務遷移後，用 traffic split 從 0% 開始逐步把流量導向新叢集。觀察 per-service error rate 與 latency，確認穩定後提高比例。</li>
<li><strong>保留 rollback 路徑</strong>：舊叢集服務不立即下線，traffic split 隨時可切回 100% 舊叢集。rollback 操作是調整 split 比例，不需要重新部署。</li>
<li><strong>遷移完成後拆除 mesh bridge</strong>：所有服務切換完成且穩定觀測後，移除跨叢集 Linkerd 連線，舊叢集下線。</li>
</ol>
<h2 id="可回寫的章節段落">可回寫的章節段落</h2>
<ul>
<li><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移</a>：traffic split 的分批切換與回退策略</li>
<li><a href="/blog/backend/05-deployment-platform/service-discovery/" data-link-title="5.4 service discovery" data-link-desc="整理 endpoint discovery 與 DNS">5.4 跨叢集 Discovery</a>：Linkerd mirrored service 是跨叢集 discovery 的 service mesh federation 做法</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a>：每批切換的放行條件與停損訊號</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/containers/tradeshifts-migration-to-amazon-eks-without-downtime-using-linkerd/">Tradeshift migration to EKS without downtime using Linkerd</a></li>
</ul>
]]></content:encoded></item><item><title>7.C1 Cloudflare：2026 Route Leak 事件</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-route-leak-2026/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-route-leak-2026/</guid><description>&lt;p>這個案例的核心責任是把網路控制面事件轉換成治理層可操作條件。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Cloudflare 在 2026-01-22 發生 route leak，成因是自動化路由政策配置錯誤，導致流量擁塞與延遲提升。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>控制面自動化帶來速度，也提高錯誤一次性放大的風險。關鍵是補強變更守門與回復策略，停止自動化會退回更差的狀態。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>路由政策變更要有 pre-check 與 blast radius 評估。&lt;/li>
&lt;li>建立快速撤回機制與明確責任路由。&lt;/li>
&lt;li>把同類事件寫入 tripwire，觸發強制重評估。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14 governance exception/tripwire&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 containment/recovery&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/route-leak-incident-january-22-2026/">Cloudflare route leak incident (2026-01-23)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把網路控制面事件轉換成治理層可操作條件。</p>
<h2 id="觀察">觀察</h2>
<p>Cloudflare 在 2026-01-22 發生 route leak，成因是自動化路由政策配置錯誤，導致流量擁塞與延遲提升。</p>
<h2 id="判讀">判讀</h2>
<p>控制面自動化帶來速度，也提高錯誤一次性放大的風險。關鍵是補強變更守門與回復策略，停止自動化會退回更差的狀態。</p>
<h2 id="策略">策略</h2>
<ol>
<li>路由政策變更要有 pre-check 與 blast radius 評估。</li>
<li>建立快速撤回機制與明確責任路由。</li>
<li>把同類事件寫入 tripwire，觸發強制重評估。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14 governance exception/tripwire</a> 與 <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 containment/recovery</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/route-leak-incident-january-22-2026/">Cloudflare route leak incident (2026-01-23)</a></li>
</ul>
]]></content:encoded></item><item><title>Atlassian 2022 April Multi-tenant Deletion Outage</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/</guid><description>&lt;p>Atlassian 2022 事故的核心教訓是：在多租戶 SaaS 中，誤刪不只是一個資料問題，而是恢復編排、客戶通訊與跨團隊協調同時失效的系統級事件。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Atlassian 官方 PIR 指出，2022-04-05 起有 775 客戶受影響，部分恢復歷時長達 14 天。事故起因是維運腳本使用了錯誤識別資訊，導致站點被刪除，後續需要多工作流並行恢復與驗證。&lt;/p>
&lt;p>事件特徵是「影響客戶數有限，但每一個客戶的恢復成本高」，因此恢復策略必須分批與分層。&lt;/p>
&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>已是 tenant 級資料生命週期事件&lt;/td>
 &lt;td>立即升級 major incident&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;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>維運腳本操作錯誤導致多租戶站點被刪除。&lt;/li>
&lt;li>客戶無法存取產品並建立支援事件。&lt;/li>
&lt;li>事故升級後成立跨職能指揮團隊，24x7 推進恢復。&lt;/li>
&lt;li>恢復以分批方式進行，並持續更新 status 與客戶通訊。&lt;/li>
&lt;li>事後回寫到 soft delete、恢復自動化與通訊流程改善。&lt;/li>
&lt;/ol>
&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>Script safety guardrail&lt;/td>
 &lt;td>腳本輸入與刪除對象校驗不足&lt;/td>
 &lt;td>高風險刪除操作增加雙重驗證與範圍確認&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-tenant restore orchestration&lt;/td>
 &lt;td>大規模租戶恢復缺少標準化分批流程&lt;/td>
 &lt;td>建立恢復編排工具與租戶優先序模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Data restoration consistency&lt;/td>
 &lt;td>恢復點一致性在早期流程中不穩&lt;/td>
 &lt;td>增加恢復後一致性審核與回補流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident communication resilience&lt;/td>
 &lt;td>長事故中的客戶通訊節奏與聯絡資料治理&lt;/td>
 &lt;td>固定 cadence、改善受影響客戶聯絡資訊可得性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>客戶影響評估： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment&lt;/a>&lt;/li>
&lt;li>事中決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>穩態與恢復完成： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/post-incident-review-april-2022-outage">Post-Incident Review on the Atlassian April 2022 outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/april-2022-outage-update">Update on the Atlassian outage affecting some customers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Atlassian 2022 事故的核心教訓是：在多租戶 SaaS 中，誤刪不只是一個資料問題，而是恢復編排、客戶通訊與跨團隊協調同時失效的系統級事件。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Atlassian 官方 PIR 指出，2022-04-05 起有 775 客戶受影響，部分恢復歷時長達 14 天。事故起因是維運腳本使用了錯誤識別資訊，導致站點被刪除，後續需要多工作流並行恢復與驗證。</p>
<p>事件特徵是「影響客戶數有限，但每一個客戶的恢復成本高」，因此恢復策略必須分批與分層。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶站點直接不可用</td>
          <td>已是 tenant 級資料生命週期事件</td>
          <td>立即升級 major incident</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>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>維運腳本操作錯誤導致多租戶站點被刪除。</li>
<li>客戶無法存取產品並建立支援事件。</li>
<li>事故升級後成立跨職能指揮團隊，24x7 推進恢復。</li>
<li>恢復以分批方式進行，並持續更新 status 與客戶通訊。</li>
<li>事後回寫到 soft delete、恢復自動化與通訊流程改善。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Script safety guardrail</td>
          <td>腳本輸入與刪除對象校驗不足</td>
          <td>高風險刪除操作增加雙重驗證與範圍確認</td>
      </tr>
      <tr>
          <td>Multi-tenant restore orchestration</td>
          <td>大規模租戶恢復缺少標準化分批流程</td>
          <td>建立恢復編排工具與租戶優先序模型</td>
      </tr>
      <tr>
          <td>Data restoration consistency</td>
          <td>恢復點一致性在早期流程中不穩</td>
          <td>增加恢復後一致性審核與回補流程</td>
      </tr>
      <tr>
          <td>Incident communication resilience</td>
          <td>長事故中的客戶通訊節奏與聯絡資料治理</td>
          <td>固定 cadence、改善受影響客戶聯絡資訊可得性</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>客戶影響評估： <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment</a></li>
<li>事中決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>穩態與恢復完成： <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/post-incident-review-april-2022-outage">Post-Incident Review on the Atlassian April 2022 outage</a></li>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/april-2022-outage-update">Update on the Atlassian outage affecting some customers</a></li>
</ul>
]]></content:encoded></item><item><title>AWS S3 2017 US-EAST-1 Service Disruption</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/</guid><description>&lt;p>2017 年 AWS S3 us-east-1 事故的核心教訓是：內部操作工具若能快速移除共享子系統容量，單一命令輸入錯誤就會變成區域級控制面事故。這類事故的第一責任是限制操作 blast radius，再把恢復順序與通訊入口從受影響依賴中拆出。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>AWS 在 2017-02-28 發生 Amazon S3 Northern Virginia（US-EAST-1）服務中斷。官方摘要指出，S3 團隊當時正在排查 billing system 進度偏慢問題；9:37AM PST，一位授權 S3 團隊成員依既有 playbook 執行命令，原本只要移除少量 billing 相關子系統 server，但其中一個輸入值錯誤，導致移除的 server set 比預期大。&lt;/p>
&lt;p>被移除的 server 同時支援 S3 的 index subsystem 與 placement subsystem。index subsystem 管理該 region 內所有 S3 object 的 metadata 與位置資訊，GET、LIST、PUT、DELETE 都依賴它；placement subsystem 負責新 object 的 storage allocation，PUT 還需要它才能運作。這兩個子系統被迫完整重啟，導致 S3 API 在重啟期間無法正常服務。&lt;/p>
&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>GET / LIST / PUT / DELETE 同時異常&lt;/td>
 &lt;td>index subsystem 已成為共同故障點&lt;/td>
 &lt;td>優先判斷 metadata / index 層，而非單一 API&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PUT 恢復晚於 GET / LIST / DELETE&lt;/td>
 &lt;td>placement subsystem 仍未完成恢復&lt;/td>
 &lt;td>對外通訊要分操作類型描述恢復狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>EC2 launch、EBS snapshot、Lambda 受影響&lt;/td>
 &lt;td>S3 是多服務共享依賴&lt;/td>
 &lt;td>incident scope 需要擴到 dependent services&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Service Health Dashboard 更新受阻&lt;/td>
 &lt;td>狀態頁管理入口依賴受影響服務&lt;/td>
 &lt;td>立即切到獨立通訊路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重啟時間超過預期&lt;/td>
 &lt;td>大型子系統多年未完整重啟與驗證&lt;/td>
 &lt;td>回寫 recovery rehearsal 與 cell partition&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>S3 團隊排查 billing system 進度偏慢問題。&lt;/li>
&lt;li>授權成員依既有 playbook 執行移除少量 server 的操作命令。&lt;/li>
&lt;li>命令輸入值錯誤，移除的 server set 比預期大。&lt;/li>
&lt;li>被移除容量同時支援 index subsystem 與 placement subsystem。&lt;/li>
&lt;li>兩個子系統需要完整重啟，S3 API 在重啟期間無法正常服務。&lt;/li>
&lt;li>依賴 S3 的其他 AWS 服務在 US-EAST-1 同步受影響。&lt;/li>
&lt;li>AWS 先用 AWS Twitter feed 與 Service Health Dashboard banner text 溝通，直到 SHD individual service status 可以更新。&lt;/li>
&lt;li>index subsystem 先恢復足夠容量，再逐步恢復 GET / LIST / DELETE；placement subsystem 完成後，PUT 才恢復正常。&lt;/li>
&lt;/ol>
&lt;p>這條路徑顯示：事故起點是內部操作工具缺少數量與容量下限保護，外部流量尖峰在此無關。真正放大事故的是共享子系統、區域依賴與通訊入口對同一服務的依賴。&lt;/p>
&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>對 remove / drain 類操作加速率、數量與 minimum capacity guardrail&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shared subsystem blast radius&lt;/td>
 &lt;td>billing 操作影響 index 與 placement&lt;/td>
 &lt;td>對共享子系統建立 dependency map 與 blast radius review&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recovery rehearsal&lt;/td>
 &lt;td>大型子系統多年未完整重啟，恢復時間超過預期&lt;/td>
 &lt;td>把 index / placement 類核心子系統納入定期 restart / restore rehearsal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cell partition&lt;/td>
 &lt;td>大型 region 子系統恢復成本過高&lt;/td>
 &lt;td>把核心子系統拆成較小 cell，降低單次恢復範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Status page dependency&lt;/td>
 &lt;td>SHD 管理入口依賴受影響服務&lt;/td>
 &lt;td>將 incident communication 工具跨 region 與跨依賴部署&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operation decision log&lt;/td>
 &lt;td>事中需要記錄重啟順序與 API 恢復差異&lt;/td>
 &lt;td>在 decision log 中分別記錄 index、placement 與 dependent services 狀態&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>觀測證據包： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package&lt;/a>&lt;/li>
&lt;li>實驗安全邊界： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary&lt;/a>&lt;/li>
&lt;li>穩態與恢復完成： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>止血與回復： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy&lt;/a>&lt;/li>
&lt;li>事中決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/message/41926/">Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2017 年 AWS S3 us-east-1 事故的核心教訓是：內部操作工具若能快速移除共享子系統容量，單一命令輸入錯誤就會變成區域級控制面事故。這類事故的第一責任是限制操作 blast radius，再把恢復順序與通訊入口從受影響依賴中拆出。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>AWS 在 2017-02-28 發生 Amazon S3 Northern Virginia（US-EAST-1）服務中斷。官方摘要指出，S3 團隊當時正在排查 billing system 進度偏慢問題；9:37AM PST，一位授權 S3 團隊成員依既有 playbook 執行命令，原本只要移除少量 billing 相關子系統 server，但其中一個輸入值錯誤，導致移除的 server set 比預期大。</p>
<p>被移除的 server 同時支援 S3 的 index subsystem 與 placement subsystem。index subsystem 管理該 region 內所有 S3 object 的 metadata 與位置資訊，GET、LIST、PUT、DELETE 都依賴它；placement subsystem 負責新 object 的 storage allocation，PUT 還需要它才能運作。這兩個子系統被迫完整重啟，導致 S3 API 在重啟期間無法正常服務。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GET / LIST / PUT / DELETE 同時異常</td>
          <td>index subsystem 已成為共同故障點</td>
          <td>優先判斷 metadata / index 層，而非單一 API</td>
      </tr>
      <tr>
          <td>PUT 恢復晚於 GET / LIST / DELETE</td>
          <td>placement subsystem 仍未完成恢復</td>
          <td>對外通訊要分操作類型描述恢復狀態</td>
      </tr>
      <tr>
          <td>EC2 launch、EBS snapshot、Lambda 受影響</td>
          <td>S3 是多服務共享依賴</td>
          <td>incident scope 需要擴到 dependent services</td>
      </tr>
      <tr>
          <td>Service Health Dashboard 更新受阻</td>
          <td>狀態頁管理入口依賴受影響服務</td>
          <td>立即切到獨立通訊路徑</td>
      </tr>
      <tr>
          <td>重啟時間超過預期</td>
          <td>大型子系統多年未完整重啟與驗證</td>
          <td>回寫 recovery rehearsal 與 cell partition</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>S3 團隊排查 billing system 進度偏慢問題。</li>
<li>授權成員依既有 playbook 執行移除少量 server 的操作命令。</li>
<li>命令輸入值錯誤，移除的 server set 比預期大。</li>
<li>被移除容量同時支援 index subsystem 與 placement subsystem。</li>
<li>兩個子系統需要完整重啟，S3 API 在重啟期間無法正常服務。</li>
<li>依賴 S3 的其他 AWS 服務在 US-EAST-1 同步受影響。</li>
<li>AWS 先用 AWS Twitter feed 與 Service Health Dashboard banner text 溝通，直到 SHD individual service status 可以更新。</li>
<li>index subsystem 先恢復足夠容量，再逐步恢復 GET / LIST / DELETE；placement subsystem 完成後，PUT 才恢復正常。</li>
</ol>
<p>這條路徑顯示：事故起點是內部操作工具缺少數量與容量下限保護，外部流量尖峰在此無關。真正放大事故的是共享子系統、區域依賴與通訊入口對同一服務的依賴。</p>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>操作工具安全閘門</td>
          <td>單一輸入錯誤可快速移除過多容量</td>
          <td>對 remove / drain 類操作加速率、數量與 minimum capacity guardrail</td>
      </tr>
      <tr>
          <td>Shared subsystem blast radius</td>
          <td>billing 操作影響 index 與 placement</td>
          <td>對共享子系統建立 dependency map 與 blast radius review</td>
      </tr>
      <tr>
          <td>Recovery rehearsal</td>
          <td>大型子系統多年未完整重啟，恢復時間超過預期</td>
          <td>把 index / placement 類核心子系統納入定期 restart / restore rehearsal</td>
      </tr>
      <tr>
          <td>Cell partition</td>
          <td>大型 region 子系統恢復成本過高</td>
          <td>把核心子系統拆成較小 cell，降低單次恢復範圍</td>
      </tr>
      <tr>
          <td>Status page dependency</td>
          <td>SHD 管理入口依賴受影響服務</td>
          <td>將 incident communication 工具跨 region 與跨依賴部署</td>
      </tr>
      <tr>
          <td>Operation decision log</td>
          <td>事中需要記錄重啟順序與 API 恢復差異</td>
          <td>在 decision log 中分別記錄 index、placement 與 dependent services 狀態</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>觀測證據包： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>實驗安全邊界： <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary</a></li>
<li>穩態與恢復完成： <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>止血與回復： <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy</a></li>
<li>事中決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/message/41926/">Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region</a></li>
</ul>
]]></content:encoded></item><item><title>Cloudflare 2019 Regex CPU Outage</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/</guid><description>&lt;p>2019 年 Cloudflare regex 事故的核心教訓是：控制面配置錯誤可以在秒級擴散成全球可用性事故。這類事故的第一責任不是「加機器」，而是迅速切斷擴散路徑，讓錯誤停止被新流量放大。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cloudflare 在 2019-07-02 發布新的 WAF Managed Rule 後，規則中的 regex 觸發 catastrophic backtracking，導致 edge CPU 快速打滿。事故影響約 27 分鐘，症狀是大量 502/503 與延遲激增。&lt;/p>
&lt;p>這起事件屬於典型「控制面配置推送 → data plane 全網受影響」模式。錯誤並非單點節點故障，而是由一致推送機制把同一錯誤同步擴散到整個 edge 網路。&lt;/p>
&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>全球 CPU 同步飆升&lt;/td>
 &lt;td>問題來自共用規則或共用執行路徑&lt;/td>
 &lt;td>優先檢查最新全域配置變更&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5xx 與延遲同時惡化&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>事件期間 rule path 命中異常增&lt;/td>
 &lt;td>單一規則造成 CPU 熱點&lt;/td>
 &lt;td>補 rule-level profiling 與上線前成本檢查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>控制面推送新 WAF 規則到全球 edge。&lt;/li>
&lt;li>規則 regex 在特定輸入下產生高計算成本。&lt;/li>
&lt;li>edge CPU 被規則執行成本吃滿，請求處理能力下降。&lt;/li>
&lt;li>5xx 與延遲擴散成全球可見症狀。&lt;/li>
&lt;li>回滾規則後，CPU 與可用性逐步恢復。&lt;/li>
&lt;/ol>
&lt;p>這條路徑顯示：事故擴散速度主要由「推送覆蓋範圍」決定，而不是由「單機故障率」決定。&lt;/p>
&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>regex 風險模式未被擋下&lt;/td>
 &lt;td>補 regex 風險 lint 與拒絕規則（高 backtracking 風險直接阻擋）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>上線前效能測試&lt;/td>
 &lt;td>缺少 rule-level CPU 成本基線&lt;/td>
 &lt;td>補 rule replay 測試，用代表性 payload 驗證執行成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推送策略&lt;/td>
 &lt;td>全域一次推送讓 blast radius 過大&lt;/td>
 &lt;td>改成分區/分群 staged rollout，設回滾閘門&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事故啟動門檻&lt;/td>
 &lt;td>全網症狀出現後才完整升級&lt;/td>
 &lt;td>以「跨區 CPU 同步異常 + 5xx 上升」作為自動升級條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Decision log&lt;/td>
 &lt;td>事中決策若缺時間線，復盤成本高&lt;/td>
 &lt;td>在事故期間即時記錄假設、回滾條件、責任人與驗證結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence write-back&lt;/td>
 &lt;td>事故教訓易停在 PIR 文本&lt;/td>
 &lt;td>回寫到 &lt;code>04&lt;/code> 觀測規則與 &lt;code>06&lt;/code> 實驗安全邊界，形成下次推送前硬性 gate&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>回寫訊號治理： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality&lt;/a>&lt;/li>
&lt;li>回寫規則成本訊號： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/rule-level-cpu-signal-governance/" data-link-title="4.21 Rule-level CPU Signal Governance" data-link-desc="把規則與策略執行成本變成可觀測訊號，避免控制面小變更在資料面形成 CPU 熱點。">4.21 Rule-level CPU Signal Governance&lt;/a>&lt;/li>
&lt;li>回寫規則推送閘門： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a>&lt;/li>
&lt;li>回寫驗證與安全邊界： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary&lt;/a>&lt;/li>
&lt;li>回寫事中決策與證據： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>回寫跨模組閉環： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019">Details of the Cloudflare outage on July 2, 2019&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2019 年 Cloudflare regex 事故的核心教訓是：控制面配置錯誤可以在秒級擴散成全球可用性事故。這類事故的第一責任不是「加機器」，而是迅速切斷擴散路徑，讓錯誤停止被新流量放大。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Cloudflare 在 2019-07-02 發布新的 WAF Managed Rule 後，規則中的 regex 觸發 catastrophic backtracking，導致 edge CPU 快速打滿。事故影響約 27 分鐘，症狀是大量 502/503 與延遲激增。</p>
<p>這起事件屬於典型「控制面配置推送 → data plane 全網受影響」模式。錯誤並非單點節點故障，而是由一致推送機制把同一錯誤同步擴散到整個 edge 網路。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全球 CPU 同步飆升</td>
          <td>問題來自共用規則或共用執行路徑</td>
          <td>優先檢查最新全域配置變更</td>
      </tr>
      <tr>
          <td>5xx 與延遲同時惡化</td>
          <td>非單純容量尖峰，更像執行成本突增</td>
          <td>優先撤回新規則，避免持續放大</td>
      </tr>
      <tr>
          <td>多區域同時報警</td>
          <td>事故已跨區域，屬全網級控制面風險</td>
          <td>啟動全域指揮節奏與高頻通訊</td>
      </tr>
      <tr>
          <td>回滾後指標快速回穩</td>
          <td>根因與近期變更高度相關</td>
          <td>立即凍結同批規則推送，改走分區驗證</td>
      </tr>
      <tr>
          <td>事件期間 rule path 命中異常增</td>
          <td>單一規則造成 CPU 熱點</td>
          <td>補 rule-level profiling 與上線前成本檢查</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>控制面推送新 WAF 規則到全球 edge。</li>
<li>規則 regex 在特定輸入下產生高計算成本。</li>
<li>edge CPU 被規則執行成本吃滿，請求處理能力下降。</li>
<li>5xx 與延遲擴散成全球可見症狀。</li>
<li>回滾規則後，CPU 與可用性逐步恢復。</li>
</ol>
<p>這條路徑顯示：事故擴散速度主要由「推送覆蓋範圍」決定，而不是由「單機故障率」決定。</p>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>規則上線前靜態檢查</td>
          <td>regex 風險模式未被擋下</td>
          <td>補 regex 風險 lint 與拒絕規則（高 backtracking 風險直接阻擋）</td>
      </tr>
      <tr>
          <td>上線前效能測試</td>
          <td>缺少 rule-level CPU 成本基線</td>
          <td>補 rule replay 測試，用代表性 payload 驗證執行成本</td>
      </tr>
      <tr>
          <td>推送策略</td>
          <td>全域一次推送讓 blast radius 過大</td>
          <td>改成分區/分群 staged rollout，設回滾閘門</td>
      </tr>
      <tr>
          <td>事故啟動門檻</td>
          <td>全網症狀出現後才完整升級</td>
          <td>以「跨區 CPU 同步異常 + 5xx 上升」作為自動升級條件</td>
      </tr>
      <tr>
          <td>Decision log</td>
          <td>事中決策若缺時間線，復盤成本高</td>
          <td>在事故期間即時記錄假設、回滾條件、責任人與驗證結果</td>
      </tr>
      <tr>
          <td>Evidence write-back</td>
          <td>事故教訓易停在 PIR 文本</td>
          <td>回寫到 <code>04</code> 觀測規則與 <code>06</code> 實驗安全邊界，形成下次推送前硬性 gate</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>回寫訊號治理： <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a></li>
<li>回寫規則成本訊號： <a href="/blog/backend/04-observability/rule-level-cpu-signal-governance/" data-link-title="4.21 Rule-level CPU Signal Governance" data-link-desc="把規則與策略執行成本變成可觀測訊號，避免控制面小變更在資料面形成 CPU 熱點。">4.21 Rule-level CPU Signal Governance</a></li>
<li>回寫規則推送閘門： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a></li>
<li>回寫驗證與安全邊界： <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary</a></li>
<li>回寫事中決策與證據： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>回寫跨模組閉環： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019">Details of the Cloudflare outage on July 2, 2019</a></li>
</ul>
]]></content:encoded></item><item><title>Fastly 2021 June Global Edge Config-triggered Outage</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/</guid><description>&lt;p>Fastly 2021 事故的核心教訓是：在全球 edge 平台中，一個有效配置也可能觸發平台潛藏 bug，造成分鐘級全球擴散。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Fastly 官方摘要指出，2021-06-08 的全球 outage 由平台既有軟體 bug 觸發，觸發條件來自一個有效的客戶配置變更。故障在短時間內影響大範圍 edge 節點，並在隔離配置後逐步恢復。&lt;/p>
&lt;p>這類事故不是「客戶配置錯誤」或「平台單點故障」的二選一，而是配置與平台行為交互下的系統性風險。&lt;/p>
&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>全球 503 快速上升&lt;/td>
 &lt;td>edge 平台共同執行路徑失效&lt;/td>
 &lt;td>立即轉全域 incident，不走單區排障&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>事故前已有潛藏 bug&lt;/td>
 &lt;td>變更驗證對交互條件覆蓋不足&lt;/td>
 &lt;td>回寫配置驗證與灰度策略&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>平台先前部署引入可被特定條件觸發的 bug。&lt;/li>
&lt;li>客戶推送有效配置，觸發 bug。&lt;/li>
&lt;li>大範圍 edge 節點回應錯誤，形成全球 outage。&lt;/li>
&lt;li>團隊定位並隔離觸發配置，服務逐步恢復。&lt;/li>
&lt;li>事後回寫驗證、隔離與恢復流程。&lt;/li>
&lt;/ol>
&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>Config-trigger safety gate&lt;/td>
 &lt;td>有效配置也可觸發平台 bug&lt;/td>
 &lt;td>對配置與平台交互條件增加回放測試&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Global propagation brake&lt;/td>
 &lt;td>擴散速度遠快於局部人工止血&lt;/td>
 &lt;td>建立全域停傳播與快速隔離機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Canary and staged rollout&lt;/td>
 &lt;td>交互條件在前期驗證未被涵蓋&lt;/td>
 &lt;td>強化灰度策略與跨場景驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident communication timing&lt;/td>
 &lt;td>影響廣但恢復快，對外節奏需精準&lt;/td>
 &lt;td>以固定 cadence 說明影響範圍與恢復進度&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>規則/配置成本訊號： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/rule-level-cpu-signal-governance/" data-link-title="4.21 Rule-level CPU Signal Governance" data-link-desc="把規則與策略執行成本變成可觀測訊號，避免控制面小變更在資料面形成 CPU 熱點。">4.21 Rule-level CPU Signal Governance&lt;/a>&lt;/li>
&lt;li>證據包： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package&lt;/a>&lt;/li>
&lt;li>規則推送閘門： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.fastly.com/blog/summary-of-june-8-outage">Summary of June 8 outage&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Fastly 2021 事故的核心教訓是：在全球 edge 平台中，一個有效配置也可能觸發平台潛藏 bug，造成分鐘級全球擴散。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Fastly 官方摘要指出，2021-06-08 的全球 outage 由平台既有軟體 bug 觸發，觸發條件來自一個有效的客戶配置變更。故障在短時間內影響大範圍 edge 節點，並在隔離配置後逐步恢復。</p>
<p>這類事故不是「客戶配置錯誤」或「平台單點故障」的二選一，而是配置與平台行為交互下的系統性風險。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全球 503 快速上升</td>
          <td>edge 平台共同執行路徑失效</td>
          <td>立即轉全域 incident，不走單區排障</td>
      </tr>
      <tr>
          <td>偵測時間短但影響面巨大</td>
          <td>擴散速度高於人工逐站處理能力</td>
          <td>優先做全域隔離與停傳播動作</td>
      </tr>
      <tr>
          <td>關閉觸發配置後快速回線</td>
          <td>觸發路徑明確、回退有效</td>
          <td>建立配置觸發型事故的快速回退標準</td>
      </tr>
      <tr>
          <td>事故前已有潛藏 bug</td>
          <td>變更驗證對交互條件覆蓋不足</td>
          <td>回寫配置驗證與灰度策略</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>平台先前部署引入可被特定條件觸發的 bug。</li>
<li>客戶推送有效配置，觸發 bug。</li>
<li>大範圍 edge 節點回應錯誤，形成全球 outage。</li>
<li>團隊定位並隔離觸發配置，服務逐步恢復。</li>
<li>事後回寫驗證、隔離與恢復流程。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Config-trigger safety gate</td>
          <td>有效配置也可觸發平台 bug</td>
          <td>對配置與平台交互條件增加回放測試</td>
      </tr>
      <tr>
          <td>Global propagation brake</td>
          <td>擴散速度遠快於局部人工止血</td>
          <td>建立全域停傳播與快速隔離機制</td>
      </tr>
      <tr>
          <td>Canary and staged rollout</td>
          <td>交互條件在前期驗證未被涵蓋</td>
          <td>強化灰度策略與跨場景驗證</td>
      </tr>
      <tr>
          <td>Incident communication timing</td>
          <td>影響廣但恢復快，對外節奏需精準</td>
          <td>以固定 cadence 說明影響範圍與恢復進度</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>規則/配置成本訊號： <a href="/blog/backend/04-observability/rule-level-cpu-signal-governance/" data-link-title="4.21 Rule-level CPU Signal Governance" data-link-desc="把規則與策略執行成本變成可觀測訊號，避免控制面小變更在資料面形成 CPU 熱點。">4.21 Rule-level CPU Signal Governance</a></li>
<li>證據包： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>規則推送閘門： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.fastly.com/blog/summary-of-june-8-outage">Summary of June 8 outage</a></li>
</ul>
]]></content:encoded></item><item><title>FinTech：合規壓力下的後端選型</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cases/fintech-compliance-and-selection-pressure/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cases/fintech-compliance-and-selection-pressure/</guid><description>&lt;p>這個案例的核心責任是把合規壓力轉成選型條件。FinTech 場景下，資料保留、審計追溯與交易一致性通常比純效能優先。&lt;/p>
&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>audit evidence gap&lt;/td>
 &lt;td>稽核證據是否連續&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/security-data-protection-requirements/" data-link-title="0.8 資安與資料保護需求" data-link-desc="從權限分級、伺服器防護、資料遮罩、傳輸保護與稽核設計安全邊界">0.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>duplicate transaction risk&lt;/td>
 &lt;td>重試是否可能造成雙重結果&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>release freeze frequency&lt;/td>
 &lt;td>發布是否常因風險臨時凍結&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="風險與邊界">風險與邊界&lt;/h2>
&lt;p>把合規當成部署後補強會抬高長期成本。較穩定的做法是在選型時就定義證據鏈、資料邊界與回復順序，避免後續跨模組反覆返工。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先補 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12&lt;/a> 的審計訊號，再用 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a> 定義合規變更門檻。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是把合規壓力轉成選型條件。FinTech 場景下，資料保留、審計追溯與交易一致性通常比純效能優先。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>audit evidence gap</td>
          <td>稽核證據是否連續</td>
          <td><a href="/blog/backend/00-service-selection/security-data-protection-requirements/" data-link-title="0.8 資安與資料保護需求" data-link-desc="從權限分級、伺服器防護、資料遮罩、傳輸保護與稽核設計安全邊界">0.8</a></td>
      </tr>
      <tr>
          <td>duplicate transaction risk</td>
          <td>重試是否可能造成雙重結果</td>
          <td><a href="/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2</a></td>
      </tr>
      <tr>
          <td>release freeze frequency</td>
          <td>發布是否常因風險臨時凍結</td>
          <td><a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6</a></td>
      </tr>
  </tbody>
</table>
<h2 id="風險與邊界">風險與邊界</h2>
<p>把合規當成部署後補強會抬高長期成本。較穩定的做法是在選型時就定義證據鏈、資料邊界與回復順序，避免後續跨模組反覆返工。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先補 <a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12</a> 的審計訊號，再用 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a> 定義合規變更門檻。</p>
]]></content:encoded></item><item><title>FinTech：審計證據鏈的可觀測性設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/fintech-audit-evidence-observability/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/fintech-audit-evidence-observability/</guid><description>&lt;p>本案例的核心責任是讓審計證據與運維訊號共用同一套資料邊界。FinTech 場景下，觀測資料不只是除錯用途，也是合規證據基礎。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一家處理線上支付的金融科技公司，每日交易量約 200 萬筆，涵蓋信用卡收單、轉帳與退款。每季有外部稽核查核交易處理的完整性與存取控制，事故發生時法務需要在 48 小時內提供特定交易的完整處理鏈證據。&lt;/p>
&lt;p>初期系統把所有 log 寫到同一個 log group — application debug、request trace、交易狀態變更與使用者存取紀錄全混在一起。稽核人員要從數 TB 的 log 中撈出特定交易的完整軌跡，每次查詢耗時數小時。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="operational-log-與-audit-log-混合">Operational log 與 audit log 混合&lt;/h3>
&lt;p>Application log 記錄 debug 資訊（SQL timing、cache hit/miss、retry），audit log 記錄業務事件（交易建立、狀態變更、存取紀錄）。兩者混在同一個 pipeline 時，retention 策略互相衝突 — debug log 留 14 天夠用，但 audit log 法規要求保留 5 年。統一設成 5 年讓儲存成本暴增，統一設成 14 天則遺失合規證據。&lt;/p>
&lt;h3 id="pii-暴露在-log-中">PII 暴露在 log 中&lt;/h3>
&lt;p>早期 log 直接印出 request body，信用卡號跟身分證字號散落在各種 log entry。稽核指出 PII 在 log 系統中的暴露面超過業務需要，但 log 已經寫入後無法回溯修改。&lt;/p>
&lt;h3 id="event-correlation-斷裂">Event correlation 斷裂&lt;/h3>
&lt;p>交易從建立到完成經過多個服務（checkout-api → payment-gateway → settlement → notification），但各服務的 log 使用不同的 correlation key。Checkout 用 &lt;code>order_id&lt;/code>，payment-gateway 用 &lt;code>payment_ref&lt;/code>，settlement 用自己的 &lt;code>batch_id&lt;/code>。稽核要求「給我交易 X 的完整處理鏈」時，工程師需要手動在三個系統各自查詢再人工拼接。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="audit-log-分離">Audit log 分離&lt;/h3>
&lt;p>把 audit event 獨立到專屬 pipeline：交易狀態變更、使用者存取、權限變動、退款操作各自產生結構化 audit event，寫入 immutable storage（append-only、禁止刪除與修改）。Operational log 維持 14 天 retention，audit log 走 5 年 retention + cold archive。&lt;/p>
&lt;p>分離的判準是「這筆紀錄是否可能被稽核或法務要求提供」。是 → audit pipeline；否 → operational pipeline。灰色地帶（例如認證失敗 log）歸入 audit pipeline — 寧可多留不可少留。&lt;/p>
&lt;h3 id="pii-redaction-pipeline">PII redaction pipeline&lt;/h3>
&lt;p>在 log ingestion 階段加入 redaction processor：信用卡號遮罩為末四碼、身分證字號完全移除、email 保留 domain 遮罩使用者名稱。Redaction 發生在寫入儲存之前，原始資料不落地。&lt;/p>
&lt;p>需要完整 PII 的場景（如詐欺調查）走另一條授權存取管道，跟觀測 pipeline 分離。&lt;/p>
&lt;h3 id="統一-correlation-key">統一 correlation key&lt;/h3>
&lt;p>所有服務在交易入口處產生 &lt;code>trace_id&lt;/code> 和 &lt;code>transaction_id&lt;/code>，兩個 key 同時寫入每一筆 audit event 和 operational log。稽核查詢用 &lt;code>transaction_id&lt;/code> 就能撈出跨服務的完整處理鏈，不需要手動拼接。&lt;/p>
&lt;h2 id="取捨">取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>混合 pipeline&lt;/th>
 &lt;th>分離 pipeline&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>建置成本&lt;/td>
 &lt;td>低（一套 pipeline）&lt;/td>
 &lt;td>中（兩套 pipeline + routing 邏輯）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>儲存成本&lt;/td>
 &lt;td>高（全部用最長 retention）&lt;/td>
 &lt;td>可控（各自 retention）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查詢效率&lt;/td>
 &lt;td>低（audit event 淹沒在 debug log 中）&lt;/td>
 &lt;td>高（audit 獨立查詢）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>合規風險&lt;/td>
 &lt;td>高（PII 暴露面大、retention 可能不足）&lt;/td>
 &lt;td>低（PII redacted、retention 對齊法規）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維運複雜度&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中（需維護 routing 規則與 redaction 規則）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分離 pipeline 的最大成本在 routing 規則的維護 — 新服務上線時要確認 audit event 走對 pipeline。解法是在 SDK 層提供 &lt;code>emit_audit_event()&lt;/code> 函式，讓 routing 在 producer 端決定，不依賴下游 pipeline 的內容判斷。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是讓審計證據與運維訊號共用同一套資料邊界。FinTech 場景下，觀測資料不只是除錯用途，也是合規證據基礎。</p>
<h2 id="業務背景">業務背景</h2>
<p>一家處理線上支付的金融科技公司，每日交易量約 200 萬筆，涵蓋信用卡收單、轉帳與退款。每季有外部稽核查核交易處理的完整性與存取控制，事故發生時法務需要在 48 小時內提供特定交易的完整處理鏈證據。</p>
<p>初期系統把所有 log 寫到同一個 log group — application debug、request trace、交易狀態變更與使用者存取紀錄全混在一起。稽核人員要從數 TB 的 log 中撈出特定交易的完整軌跡，每次查詢耗時數小時。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="operational-log-與-audit-log-混合">Operational log 與 audit log 混合</h3>
<p>Application log 記錄 debug 資訊（SQL timing、cache hit/miss、retry），audit log 記錄業務事件（交易建立、狀態變更、存取紀錄）。兩者混在同一個 pipeline 時，retention 策略互相衝突 — debug log 留 14 天夠用，但 audit log 法規要求保留 5 年。統一設成 5 年讓儲存成本暴增，統一設成 14 天則遺失合規證據。</p>
<h3 id="pii-暴露在-log-中">PII 暴露在 log 中</h3>
<p>早期 log 直接印出 request body，信用卡號跟身分證字號散落在各種 log entry。稽核指出 PII 在 log 系統中的暴露面超過業務需要，但 log 已經寫入後無法回溯修改。</p>
<h3 id="event-correlation-斷裂">Event correlation 斷裂</h3>
<p>交易從建立到完成經過多個服務（checkout-api → payment-gateway → settlement → notification），但各服務的 log 使用不同的 correlation key。Checkout 用 <code>order_id</code>，payment-gateway 用 <code>payment_ref</code>，settlement 用自己的 <code>batch_id</code>。稽核要求「給我交易 X 的完整處理鏈」時，工程師需要手動在三個系統各自查詢再人工拼接。</p>
<h2 id="解法">解法</h2>
<h3 id="audit-log-分離">Audit log 分離</h3>
<p>把 audit event 獨立到專屬 pipeline：交易狀態變更、使用者存取、權限變動、退款操作各自產生結構化 audit event，寫入 immutable storage（append-only、禁止刪除與修改）。Operational log 維持 14 天 retention，audit log 走 5 年 retention + cold archive。</p>
<p>分離的判準是「這筆紀錄是否可能被稽核或法務要求提供」。是 → audit pipeline；否 → operational pipeline。灰色地帶（例如認證失敗 log）歸入 audit pipeline — 寧可多留不可少留。</p>
<h3 id="pii-redaction-pipeline">PII redaction pipeline</h3>
<p>在 log ingestion 階段加入 redaction processor：信用卡號遮罩為末四碼、身分證字號完全移除、email 保留 domain 遮罩使用者名稱。Redaction 發生在寫入儲存之前，原始資料不落地。</p>
<p>需要完整 PII 的場景（如詐欺調查）走另一條授權存取管道，跟觀測 pipeline 分離。</p>
<h3 id="統一-correlation-key">統一 correlation key</h3>
<p>所有服務在交易入口處產生 <code>trace_id</code> 和 <code>transaction_id</code>，兩個 key 同時寫入每一筆 audit event 和 operational log。稽核查詢用 <code>transaction_id</code> 就能撈出跨服務的完整處理鏈，不需要手動拼接。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>混合 pipeline</th>
          <th>分離 pipeline</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建置成本</td>
          <td>低（一套 pipeline）</td>
          <td>中（兩套 pipeline + routing 邏輯）</td>
      </tr>
      <tr>
          <td>儲存成本</td>
          <td>高（全部用最長 retention）</td>
          <td>可控（各自 retention）</td>
      </tr>
      <tr>
          <td>查詢效率</td>
          <td>低（audit event 淹沒在 debug log 中）</td>
          <td>高（audit 獨立查詢）</td>
      </tr>
      <tr>
          <td>合規風險</td>
          <td>高（PII 暴露面大、retention 可能不足）</td>
          <td>低（PII redacted、retention 對齊法規）</td>
      </tr>
      <tr>
          <td>維運複雜度</td>
          <td>低</td>
          <td>中（需維護 routing 規則與 redaction 規則）</td>
      </tr>
  </tbody>
</table>
<p>分離 pipeline 的最大成本在 routing 規則的維護 — 新服務上線時要確認 audit event 走對 pipeline。解法是在 SDK 層提供 <code>emit_audit_event()</code> 函式，讓 routing 在 producer 端決定，不依賴下游 pipeline 的內容判斷。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a>：audit log 分離的設計原則與 PII 治理。</li>
<li><a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a>：把 audit trail 包成可交接的 evidence package。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：audit pipeline 的 ownership 歸 platform team 還是 compliance team。</li>
<li><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3 Tracing Context</a>：跨服務 correlation key 的 propagation 設計。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>稽核或法務要求提供某筆交易的完整處理鏈，工程師需要超過 1 小時才能拼出來</li>
<li>Log retention 設定跟法規要求不一致，但沒人確切知道差多少</li>
<li>PII 出現在 log search 結果中，但沒有系統性的遮罩機制</li>
<li>Application log 跟 audit log 用同一套 retention policy，儲存成本持續上升但沒人敢縮短</li>
<li>事故後法務要證據，發現關鍵時段的 log 已經因為 retention 過期而被刪除</li>
</ul>
]]></content:encoded></item><item><title>GCP 2019 US Network Congestion Multi-service Incident</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/</guid><description>&lt;p>2019 年 GCP 網路壅塞事故的核心教訓是：當共享網路容量被打滿，影響會跨越產品邊界，同一時間出現在 compute、storage、observability 與管理面。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Google Cloud 在 2019-06-02 發生美國多區域 network congestion，官方摘要指出多個 US region 出現 elevated packet loss，影響持續約 3 至 4 小時以上，並牽動多個 GCP 與非 Cloud 服務。&lt;/p>
&lt;p>這類事故本質是共享網路資源退化造成的跨產品連鎖事件，單一服務壞掉反而好處理。&lt;/p>
&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>多區域 packet loss 同時上升&lt;/td>
 &lt;td>共享網路層失衡，不是單服務 bug&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>部分 region 正常、部分 region 退化&lt;/td>
 &lt;td>區域差異可用來做流量重新分配&lt;/td>
 &lt;td>啟動 region-aware mitigation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>status page 更新中提到 varied impact&lt;/td>
 &lt;td>影響面非均勻分布&lt;/td>
 &lt;td>對外更新要分 region / service 粒度&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>美國多區域網路容量在高壓下出現壅塞與丟包。&lt;/li>
&lt;li>多個 GCP 產品受同一網路瓶頸影響，出現延遲與錯誤。&lt;/li>
&lt;li>工程團隊進行流量與容量調整，逐區域回復。&lt;/li>
&lt;li>狀態頁持續更新受影響範圍與恢復進度。&lt;/li>
&lt;li>事後回寫區域隔離、容量保留與跨產品協調流程。&lt;/li>
&lt;/ol>
&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>Region-aware traffic control&lt;/td>
 &lt;td>區域壅塞時流量轉移策略不夠快&lt;/td>
 &lt;td>建立區域流量切換的預設策略與演練&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cross-product incident command&lt;/td>
 &lt;td>多產品同時受影響時協調成本高&lt;/td>
 &lt;td>強化跨產品指揮節奏與共享 decision log&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Network dependency mapping&lt;/td>
 &lt;td>服務依賴共享網路層但判讀入口分散&lt;/td>
 &lt;td>補跨產品依賴圖與共同告警面板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Status communication granularity&lt;/td>
 &lt;td>對外說明若只寫全域狀態會失真&lt;/td>
 &lt;td>更新按 region 與 service 分層揭露&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>觀測證據包： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>事中決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>實驗安全邊界： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://status.cloud.google.com/incident/cloud-networking/19009">Google Cloud Networking Incident #19009&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/blog/topics/inside-google-cloud/an-update-on-sundays-service-disruption">An update on Sunday’s service disruption&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2019 年 GCP 網路壅塞事故的核心教訓是：當共享網路容量被打滿，影響會跨越產品邊界，同一時間出現在 compute、storage、observability 與管理面。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Google Cloud 在 2019-06-02 發生美國多區域 network congestion，官方摘要指出多個 US region 出現 elevated packet loss，影響持續約 3 至 4 小時以上，並牽動多個 GCP 與非 Cloud 服務。</p>
<p>這類事故本質是共享網路資源退化造成的跨產品連鎖事件，單一服務壞掉反而好處理。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多區域 packet loss 同時上升</td>
          <td>共享網路層失衡，不是單服務 bug</td>
          <td>優先走區域隔離與流量調整路徑</td>
      </tr>
      <tr>
          <td>多產品錯誤率一起上升</td>
          <td>事故已跨產品依賴鏈擴散</td>
          <td>事故分級以跨產品影響為主，而非單團隊視角</td>
      </tr>
      <tr>
          <td>部分 region 正常、部分 region 退化</td>
          <td>區域差異可用來做流量重新分配</td>
          <td>啟動 region-aware mitigation</td>
      </tr>
      <tr>
          <td>status page 更新中提到 varied impact</td>
          <td>影響面非均勻分布</td>
          <td>對外更新要分 region / service 粒度</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>美國多區域網路容量在高壓下出現壅塞與丟包。</li>
<li>多個 GCP 產品受同一網路瓶頸影響，出現延遲與錯誤。</li>
<li>工程團隊進行流量與容量調整，逐區域回復。</li>
<li>狀態頁持續更新受影響範圍與恢復進度。</li>
<li>事後回寫區域隔離、容量保留與跨產品協調流程。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Region-aware traffic control</td>
          <td>區域壅塞時流量轉移策略不夠快</td>
          <td>建立區域流量切換的預設策略與演練</td>
      </tr>
      <tr>
          <td>Cross-product incident command</td>
          <td>多產品同時受影響時協調成本高</td>
          <td>強化跨產品指揮節奏與共享 decision log</td>
      </tr>
      <tr>
          <td>Network dependency mapping</td>
          <td>服務依賴共享網路層但判讀入口分散</td>
          <td>補跨產品依賴圖與共同告警面板</td>
      </tr>
      <tr>
          <td>Status communication granularity</td>
          <td>對外說明若只寫全域狀態會失真</td>
          <td>更新按 region 與 service 分層揭露</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>觀測證據包： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>事中決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>實驗安全邊界： <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://status.cloud.google.com/incident/cloud-networking/19009">Google Cloud Networking Incident #19009</a></li>
<li><a href="https://cloud.google.com/blog/topics/inside-google-cloud/an-update-on-sundays-service-disruption">An update on Sunday’s service disruption</a></li>
</ul>
]]></content:encoded></item><item><title>GitHub 2018 Oct21 MySQL Topology Incident</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/</guid><description>&lt;p>2018 年 GitHub Oct21 事故的核心教訓是：跨區資料庫在 network partition 後，最困難的是如何在可用性與資料一致性之間做出可回放的決策，切換本身只是其中一步。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>GitHub 在 2018-10-21 22:52 UTC 因例行網路設備維護引發 network partition，導致跨區 MySQL replication topology 進入異常狀態。應用層在切換後持續寫入新主站，形成跨區未對齊寫入，事故最終歷時約 24 小時 11 分鐘。&lt;/p>
&lt;p>官方 post-incident analysis 指出，團隊選擇 fail-forward，而不是直接切回原主站，原因是要優先保護資料完整性，避免產生更大不一致。&lt;/p>
&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>replication topology 已跨區失衡&lt;/td>
 &lt;td>先凍結變更與部署，避免拓撲再變化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Orchestrator 顯示非預期跨區主從關係&lt;/td>
 &lt;td>自動切換已進入複雜狀態&lt;/td>
 &lt;td>轉人工決策，先保資料一致性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>webhook / Pages backlog 快速累積&lt;/td>
 &lt;td>控制面與資料面都受影響&lt;/td>
 &lt;td>將積壓處理納入恢復計畫，而非只看 API 健康度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>status 更新頻率下降&lt;/td>
 &lt;td>指揮資訊與恢復節奏未對齊&lt;/td>
 &lt;td>補 decision log 與分階段狀態更新&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>例行網路設備維護造成 East 與主資料中心連線中斷。&lt;/li>
&lt;li>Orchestrator 在 partition 下進行主從重新選舉與切換。&lt;/li>
&lt;li>連線恢復後，應用寫入已落在新主站，形成跨站寫入差異。&lt;/li>
&lt;li>團隊凍結部署並轉人工處理拓撲與一致性風險。&lt;/li>
&lt;li>選擇 fail-forward，逐步恢復服務與處理 backlog。&lt;/li>
&lt;li>事故結束後回寫跨資料中心設計、通訊粒度與演練策略。&lt;/li>
&lt;/ol>
&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>Cross-DC replication guardrail&lt;/td>
 &lt;td>partition 後拓撲變更過快&lt;/td>
 &lt;td>增加拓撲變更保護與人工切換門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consistency-first decision path&lt;/td>
 &lt;td>可用性與一致性取捨缺標準化準則&lt;/td>
 &lt;td>在 decision log 固定記錄 fail-forward / fail-back 判準&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Backlog recovery strategy&lt;/td>
 &lt;td>webhook / Pages 積壓恢復節奏缺共識&lt;/td>
 &lt;td>將 backlog drain 納入 recovery completion 定義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident communication granularity&lt;/td>
 &lt;td>只用單一顏色狀態無法表達部分恢復&lt;/td>
 &lt;td>對外更新按子服務與恢復階段拆分&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>止血與回復： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy&lt;/a>&lt;/li>
&lt;li>事中決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>資料庫轉換實作： &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 資料庫轉換實作&lt;/a>&lt;/li>
&lt;li>Migration rollout evidence： &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據&lt;/a>&lt;/li>
&lt;li>選型決策層： &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/" data-link-title="營運後技術轉換：語言、工具與架構何時該換" data-link-desc="服務營運一段時間後，如何判讀何時該轉語言、工具或架構，並用案例說明轉換動機。">0.C4 營運後技術轉換&lt;/a>&lt;/li>
&lt;li>穩態與恢復完成： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.blog/2018-10-30-oct21-post-incident-analysis/">October 21 post-incident analysis&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.blog/news-insights/company-news/october21-incident-report/">October 21 Incident Report&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2018 年 GitHub Oct21 事故的核心教訓是：跨區資料庫在 network partition 後，最困難的是如何在可用性與資料一致性之間做出可回放的決策，切換本身只是其中一步。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>GitHub 在 2018-10-21 22:52 UTC 因例行網路設備維護引發 network partition，導致跨區 MySQL replication topology 進入異常狀態。應用層在切換後持續寫入新主站，形成跨區未對齊寫入，事故最終歷時約 24 小時 11 分鐘。</p>
<p>官方 post-incident analysis 指出，團隊選擇 fail-forward，而不是直接切回原主站，原因是要優先保護資料完整性，避免產生更大不一致。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多個服務同時顯示資料過舊或不一致</td>
          <td>replication topology 已跨區失衡</td>
          <td>先凍結變更與部署，避免拓撲再變化</td>
      </tr>
      <tr>
          <td>Orchestrator 顯示非預期跨區主從關係</td>
          <td>自動切換已進入複雜狀態</td>
          <td>轉人工決策，先保資料一致性</td>
      </tr>
      <tr>
          <td>webhook / Pages backlog 快速累積</td>
          <td>控制面與資料面都受影響</td>
          <td>將積壓處理納入恢復計畫，而非只看 API 健康度</td>
      </tr>
      <tr>
          <td>status 更新頻率下降</td>
          <td>指揮資訊與恢復節奏未對齊</td>
          <td>補 decision log 與分階段狀態更新</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>例行網路設備維護造成 East 與主資料中心連線中斷。</li>
<li>Orchestrator 在 partition 下進行主從重新選舉與切換。</li>
<li>連線恢復後，應用寫入已落在新主站，形成跨站寫入差異。</li>
<li>團隊凍結部署並轉人工處理拓撲與一致性風險。</li>
<li>選擇 fail-forward，逐步恢復服務與處理 backlog。</li>
<li>事故結束後回寫跨資料中心設計、通訊粒度與演練策略。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cross-DC replication guardrail</td>
          <td>partition 後拓撲變更過快</td>
          <td>增加拓撲變更保護與人工切換門檻</td>
      </tr>
      <tr>
          <td>Consistency-first decision path</td>
          <td>可用性與一致性取捨缺標準化準則</td>
          <td>在 decision log 固定記錄 fail-forward / fail-back 判準</td>
      </tr>
      <tr>
          <td>Backlog recovery strategy</td>
          <td>webhook / Pages 積壓恢復節奏缺共識</td>
          <td>將 backlog drain 納入 recovery completion 定義</td>
      </tr>
      <tr>
          <td>Incident communication granularity</td>
          <td>只用單一顏色狀態無法表達部分恢復</td>
          <td>對外更新按子服務與恢復階段拆分</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>止血與回復： <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy</a></li>
<li>事中決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>資料庫轉換實作： <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 資料庫轉換實作</a></li>
<li>Migration rollout evidence： <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據</a></li>
<li>選型決策層： <a href="/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/" data-link-title="營運後技術轉換：語言、工具與架構何時該換" data-link-desc="服務營運一段時間後，如何判讀何時該轉語言、工具或架構，並用案例說明轉換動機。">0.C4 營運後技術轉換</a></li>
<li>穩態與恢復完成： <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.blog/2018-10-30-oct21-post-incident-analysis/">October 21 post-incident analysis</a></li>
<li><a href="https://github.blog/news-insights/company-news/october21-incident-report/">October 21 Incident Report</a></li>
</ul>
]]></content:encoded></item><item><title>Roblox 2021 Oct Prolonged Core Infra Outage</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/</guid><description>&lt;p>Roblox 2021 事故的核心教訓是：當核心基礎設施在高壓下進入非預期行為，真正困難的不只是修復，而是如何在不確定根因下維持可驗證的恢復節奏。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Roblox 在 2021-10-28 至 2021-10-31 經歷長時間服務中斷。官方更新指出問題來自內部系統在高負載下的細微通訊 bug 與連鎖壓力，不是外部攻擊或流量尖峰事件。&lt;/p>
&lt;p>這類 prolonged outage 的特徵是：初期根因不明、修復需分階段、恢復後仍有長尾調整。&lt;/p>
&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>立即升級全域 incident&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;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>平台在高負載場景下出現核心基礎設施壓力失衡。&lt;/li>
&lt;li>使用者面大量失敗，服務不可用。&lt;/li>
&lt;li>團隊跨功能長時間排查、逐步恢復基礎能力。&lt;/li>
&lt;li>恢復後持續做長尾穩定化與後續結構改善。&lt;/li>
&lt;/ol>
&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>Core dependency observability&lt;/td>
 &lt;td>核心依賴壓力與瓶頸判讀太慢&lt;/td>
 &lt;td>強化核心路徑監測與跨層證據對位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Prolonged incident command&lt;/td>
 &lt;td>長事故下節奏與交班壓力高&lt;/td>
 &lt;td>強化 IC handoff 與長事故節奏治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recovery stage definition&lt;/td>
 &lt;td>恢復完成判準不足導致反覆調整&lt;/td>
 &lt;td>用 steady state 定義分階段恢復門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Post-incident structural write-back&lt;/td>
 &lt;td>根因修補之外缺少結構性改進路徑&lt;/td>
 &lt;td>把改進落到容量、架構隔離與演練題目&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>止血與回復： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>長事故交班： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&amp;#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12 IC Handoff&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>穩態與恢復完成： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://corp.roblox.com/newsroom/2021/10/update-recent-service-outage/">An Update on Our Outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://corp.roblox.com/fr/salledepresse/2022/01/roblox-return-to-service-10-28-10-31-2021">Roblox Return to Service&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Roblox 2021 事故的核心教訓是：當核心基礎設施在高壓下進入非預期行為，真正困難的不只是修復，而是如何在不確定根因下維持可驗證的恢復節奏。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Roblox 在 2021-10-28 至 2021-10-31 經歷長時間服務中斷。官方更新指出問題來自內部系統在高負載下的細微通訊 bug 與連鎖壓力，不是外部攻擊或流量尖峰事件。</p>
<p>這類 prolonged outage 的特徵是：初期根因不明、修復需分階段、恢復後仍有長尾調整。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>平台大面積連線與操作失敗</td>
          <td>核心控制面/基礎設施層失衡</td>
          <td>立即升級全域 incident</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>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>平台在高負載場景下出現核心基礎設施壓力失衡。</li>
<li>使用者面大量失敗，服務不可用。</li>
<li>團隊跨功能長時間排查、逐步恢復基礎能力。</li>
<li>恢復後持續做長尾穩定化與後續結構改善。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Core dependency observability</td>
          <td>核心依賴壓力與瓶頸判讀太慢</td>
          <td>強化核心路徑監測與跨層證據對位</td>
      </tr>
      <tr>
          <td>Prolonged incident command</td>
          <td>長事故下節奏與交班壓力高</td>
          <td>強化 IC handoff 與長事故節奏治理</td>
      </tr>
      <tr>
          <td>Recovery stage definition</td>
          <td>恢復完成判準不足導致反覆調整</td>
          <td>用 steady state 定義分階段恢復門檻</td>
      </tr>
      <tr>
          <td>Post-incident structural write-back</td>
          <td>根因修補之外缺少結構性改進路徑</td>
          <td>把改進落到容量、架構隔離與演練題目</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>止血與回復： <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>長事故交班： <a href="/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12 IC Handoff</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>穩態與恢復完成： <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 Steady State Definition</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://corp.roblox.com/newsroom/2021/10/update-recent-service-outage/">An Update on Our Outage</a></li>
<li><a href="https://corp.roblox.com/fr/salledepresse/2022/01/roblox-return-to-service-10-28-10-31-2021">Roblox Return to Service</a></li>
</ul>
]]></content:encoded></item><item><title>AWS S3</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/</guid><description>&lt;p>AWS S3 是物件儲存的事實標準、區域控制面失效會大規模擴散到下游服務、是區域依賴 / blast radius / 控制面 vs 資料面分離的教學標竿。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>區域依賴擴散：S3 us-east-1 失效會牽動 console、IAM、ECR、CloudFormation 等控制面&lt;/li>
&lt;li>Blast radius 範例：subsystem 失效如何意外擴散到看似無關服務&lt;/li>
&lt;li>控制面 / 資料面分離設計：為何 S3 把兩者拆開、失效時表現差異&lt;/li>
&lt;li>Recovery 節奏：metadata service 重啟為何耗時、為何不能熱重啟&lt;/li>
&lt;/ul>
&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>2017&lt;/td>
 &lt;td>us-east-1 typo 4 小時&lt;/td>
 &lt;td>內部工具誤觸、區域依賴擴散&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2021&lt;/td>
 &lt;td>us-east-1 多服務退化&lt;/td>
 &lt;td>控制面與下游服務的隱性耦合&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2023&lt;/td>
 &lt;td>其他 AWS 公開摘要&lt;/td>
 &lt;td>比對 AWS post-incident report 的格式變化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/" data-link-title="AWS 2021 US-EAST-1 Control Plane Degradation" data-link-desc="2021-12-07 AWS us-east-1 控制面退化案例：內部網路壅塞、API 錯誤率升高、跨服務依賴連鎖與通訊節奏調整。">2021 US-EAST-1 Control Plane Degradation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/" data-link-title="AWS：Control Plane 事故的責任邊界與通訊節奏樣式（2023）" data-link-desc="以 AWS 2023 年公開事件樣式為主，整理 control plane 退化時如何建立責任邊界、決策紀錄與對外更新節奏。">2023 Control Plane Accountability and Communication Pattern&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/" data-link-title="AWS 2021 US-EAST-1 Control Plane Degradation" data-link-desc="2021-12-07 AWS us-east-1 控制面退化案例：內部網路壅塞、API 錯誤率升高、跨服務依賴連鎖與通訊節奏調整。">2021 US-EAST-1 Control Plane Degradation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/" data-link-title="AWS：Control Plane 事故的責任邊界與通訊節奏樣式（2023）" data-link-desc="以 AWS 2023 年公開事件樣式為主，整理 control plane 退化時如何建立責任邊界、決策紀錄與對外更新節奏。">2023 Control Plane Accountability and Communication Pattern&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>AWS S3 這個案例在講的是區域控制面失效如何透過依賴鏈條放大成多服務事故。讀者先看懂控制面與資料面分離的責任，再把 us-east-1 這類事件當成 blast radius 與恢復節奏的教學範本。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當內部工具誤觸或控制面出現異常時，第一件事是先切開受影響的依賴路徑，擴容在此階段幫助有限。當服務恢復時，metadata service 與下游依賴通常不會同時回穩，所以恢復順序比單純重啟更重要。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否分辨故障落在控制面還是資料面&lt;/li>
&lt;li>能否指出哪個依賴把事故擴成區域事件&lt;/li>
&lt;li>能否把恢復順序寫成可執行的 runbook&lt;/li>
&lt;li>能否在復原後回頭檢查 blast radius 是否被正確限制&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>AWS S3 是區域控制面事故的基準頁，和 Cloudflare、Fastly、GCP 一起讀時，最能看出「小變更如何變成大擴散」。這頁也能拿來對照 GitHub 與 Azure AD，因為它們同樣在處理共享依賴被一個節點拖垮後的恢復節奏。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2017 年 us-east-1 typo 事故顯示單一控制面誤觸可以牽動整個區域。&lt;/li>
&lt;li>2021 年 us-east-1 多服務退化則示範了控制面與下游服務如何一起受影響。&lt;/li>
&lt;li>其他公開 PIR 可以拿來對照 AWS 的回顧格式如何隨時間演化。&lt;/li>
&lt;li>S3 的案例也能對照控制面與資料面拆分後的恢復順序。&lt;/li>
&lt;li>metadata service 的恢復節奏常常比使用者看到的 outage 更長。&lt;/li>
&lt;li>region dependency 讓看似獨立的 AWS 服務一起進入失效鏈。&lt;/li>
&lt;li>blast radius 的核心是依賴鏈條被拉長後的擴散，單一服務層面的評估不足以涵蓋。&lt;/li>
&lt;li>post-incident report 的寫法能對照 AWS 如何對外說明與內部修復。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/message/41926/">Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region&lt;/a>：2017 年 S3 us-east-1 事故的官方摘要與時間線。&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-builders-library/">Introducing The Amazon Builders’ Library&lt;/a>：S3 類事故所屬的大型系統操作與恢復脈絡。&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/">Workload isolation using shuffle-sharding&lt;/a>：補 blast radius 與隔離思路。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>AWS S3 是物件儲存的事實標準、區域控制面失效會大規模擴散到下游服務、是區域依賴 / blast radius / 控制面 vs 資料面分離的教學標竿。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>區域依賴擴散：S3 us-east-1 失效會牽動 console、IAM、ECR、CloudFormation 等控制面</li>
<li>Blast radius 範例：subsystem 失效如何意外擴散到看似無關服務</li>
<li>控制面 / 資料面分離設計：為何 S3 把兩者拆開、失效時表現差異</li>
<li>Recovery 節奏：metadata service 重啟為何耗時、為何不能熱重啟</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2017</td>
          <td>us-east-1 typo 4 小時</td>
          <td>內部工具誤觸、區域依賴擴散</td>
      </tr>
      <tr>
          <td>2021</td>
          <td>us-east-1 多服務退化</td>
          <td>控制面與下游服務的隱性耦合</td>
      </tr>
      <tr>
          <td>2023</td>
          <td>其他 AWS 公開摘要</td>
          <td>比對 AWS post-incident report 的格式變化</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption</a></li>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/" data-link-title="AWS 2021 US-EAST-1 Control Plane Degradation" data-link-desc="2021-12-07 AWS us-east-1 控制面退化案例：內部網路壅塞、API 錯誤率升高、跨服務依賴連鎖與通訊節奏調整。">2021 US-EAST-1 Control Plane Degradation</a></li>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/" data-link-title="AWS：Control Plane 事故的責任邊界與通訊節奏樣式（2023）" data-link-desc="以 AWS 2023 年公開事件樣式為主，整理 control plane 退化時如何建立責任邊界、決策紀錄與對外更新節奏。">2023 Control Plane Accountability and Communication Pattern</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption</a></li>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/" data-link-title="AWS 2021 US-EAST-1 Control Plane Degradation" data-link-desc="2021-12-07 AWS us-east-1 控制面退化案例：內部網路壅塞、API 錯誤率升高、跨服務依賴連鎖與通訊節奏調整。">2021 US-EAST-1 Control Plane Degradation</a></li>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/" data-link-title="AWS：Control Plane 事故的責任邊界與通訊節奏樣式（2023）" data-link-desc="以 AWS 2023 年公開事件樣式為主，整理 control plane 退化時如何建立責任邊界、決策紀錄與對外更新節奏。">2023 Control Plane Accountability and Communication Pattern</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>AWS S3 這個案例在講的是區域控制面失效如何透過依賴鏈條放大成多服務事故。讀者先看懂控制面與資料面分離的責任，再把 us-east-1 這類事件當成 blast radius 與恢復節奏的教學範本。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當內部工具誤觸或控制面出現異常時，第一件事是先切開受影響的依賴路徑，擴容在此階段幫助有限。當服務恢復時，metadata service 與下游依賴通常不會同時回穩，所以恢復順序比單純重啟更重要。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否分辨故障落在控制面還是資料面</li>
<li>能否指出哪個依賴把事故擴成區域事件</li>
<li>能否把恢復順序寫成可執行的 runbook</li>
<li>能否在復原後回頭檢查 blast radius 是否被正確限制</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>AWS S3 是區域控制面事故的基準頁，和 Cloudflare、Fastly、GCP 一起讀時，最能看出「小變更如何變成大擴散」。這頁也能拿來對照 GitHub 與 Azure AD，因為它們同樣在處理共享依賴被一個節點拖垮後的恢復節奏。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2017 年 us-east-1 typo 事故顯示單一控制面誤觸可以牽動整個區域。</li>
<li>2021 年 us-east-1 多服務退化則示範了控制面與下游服務如何一起受影響。</li>
<li>其他公開 PIR 可以拿來對照 AWS 的回顧格式如何隨時間演化。</li>
<li>S3 的案例也能對照控制面與資料面拆分後的恢復順序。</li>
<li>metadata service 的恢復節奏常常比使用者看到的 outage 更長。</li>
<li>region dependency 讓看似獨立的 AWS 服務一起進入失效鏈。</li>
<li>blast radius 的核心是依賴鏈條被拉長後的擴散，單一服務層面的評估不足以涵蓋。</li>
<li>post-incident report 的寫法能對照 AWS 如何對外說明與內部修復。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/message/41926/">Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region</a>：2017 年 S3 us-east-1 事故的官方摘要與時間線。</li>
<li><a href="https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-builders-library/">Introducing The Amazon Builders’ Library</a>：S3 類事故所屬的大型系統操作與恢復脈絡。</li>
<li><a href="https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/">Workload isolation using shuffle-sharding</a>：補 blast radius 與隔離思路。</li>
</ul>
]]></content:encoded></item><item><title>Google</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/</guid><description>&lt;p>Google 是 SRE 概念的原始來源、SRE Book 與 Workbook 是領域 canonical text。教學重點在 SRE 工程文化、量化方法與組織節奏，單一事故只是入口。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>SLI / SLO / Error Budget：可靠性目標的量化方法、為何選 SLO 而非 100%&lt;/li>
&lt;li>Postmortem 文化：blameless / action items / 行動追蹤的閉環設計&lt;/li>
&lt;li>Toil 量化：把運維工作變成可預算的工程資產&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 與 burnout：值班輪值、shadow / primary 結構、心理安全&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> review：服務上線前的 SRE 接管門檻&lt;/li>
&lt;/ul>
&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>SRE Book Ch.1-4&lt;/td>
 &lt;td>概念基礎、為何 SLO、為何 50/50&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Postmortem Culture&lt;/td>
 &lt;td>blameless 操作化、action items 追蹤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Toil &amp;amp; Engineering Time&lt;/td>
 &lt;td>量化 toil、長期投資工程的政策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Hierarchy of Reliability&lt;/td>
 &lt;td>Monitoring → IR → PIR → Testing → Capacity → Dev → Product&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Embedded SRE / Consulting&lt;/td>
 &lt;td>SRE 介入服務的多種模式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">G1&lt;/a>&lt;/td>
 &lt;td>Error Budget 與 Release Gating&lt;/td>
 &lt;td>把 SLO 消耗量轉成放行、限速與凍結決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2&lt;/a>&lt;/td>
 &lt;td>Postmortem Closure 治理&lt;/td>
 &lt;td>把事故改進項變成可追蹤、可驗證的治理節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/" data-link-title="Google：Toil Budget 與 Automation 投資政策" data-link-desc="把 toil 從感受問題轉成預算問題：用時間配比與自動化回報機制，避免 on-call 壓力長期侵蝕可靠性工程。">G3&lt;/a>&lt;/td>
 &lt;td>Toil Budget 投資政策&lt;/td>
 &lt;td>把手動運維工作轉成可預算、可回寫的工程投資&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Google 這個案例在講的是可靠性如何變成一套可操作的工程制度，而不是單一工具或單一事故。讀者先抓到 SLI / SLO、error budget、postmortem 與 toil 這幾個原語各自負責什麼，再把它們組成一條可執行的可靠性路徑。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當服務健康開始波動時，先看 SLO 是否真的被消耗，再看監控與告警是否能對應到使用者體感。當 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 壓力升高時，重點在團隊是否把重複性工作轉成可預算的工程投資，個人技巧層面的改善幫助有限。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否用一句話說明每個 SLI 對應的使用者行為&lt;/li>
&lt;li>能否從 postmortem 找到明確 owner 與完成條件&lt;/li>
&lt;li>能否把 toil 量化成可排程的工程時間&lt;/li>
&lt;li>能否把監控、測試、容量、開發與產品決策串成同一條路由&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Google 提供的是可靠性的語言層，其他案例提供的是具體場景層。當讀者先懂 SLI / SLO 與 postmortem 這組原語，再看 Honeycomb 的 burn rate、Atlassian 的復原節奏或 GitHub 的 status communication，就能把抽象制度接到實際事故上。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>SLO 與 error budget 讓團隊把可靠性變成可量化的工程目標。&lt;/li>
&lt;li>postmortem 將事故轉成可追蹤的 action items，而不是只留下檢討文字。&lt;/li>
&lt;li>toil budget 讓重複性工作變成可預算的工程投資。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> review 讓服務在上線前先過可靠性門檻。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 與 burnout 讓值班成為組織設計問題，脫離個人耐力測試的框架。&lt;/li>
&lt;li>hierarchy of reliability 讓 monitoring、testing、capacity、dev、product 串成一條路由。&lt;/li>
&lt;li>blameless culture 讓檢討聚焦在系統與流程，而不是個人責任。&lt;/li>
&lt;li>embedded SRE / consulting 讓可靠性能力可以以不同介入深度落到服務團隊。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sre.google/">sre.google&lt;/a>：Google SRE 官方資源入口，收錄 books 與主題更新。&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/devops-sre/the-sre-book-turns-6">The SRE book turns 6!&lt;/a>：整理 SRE Book / Workbook 與延伸資源的官方入口。&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/devops-sre/how-to-design-good-slos-according-to-google-sres">Adopting SRE: Standardizing your SLO design process&lt;/a>：補 SLO 設計方法與實務語境。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Google 是 SRE 概念的原始來源、SRE Book 與 Workbook 是領域 canonical text。教學重點在 SRE 工程文化、量化方法與組織節奏，單一事故只是入口。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>SLI / SLO / Error Budget：可靠性目標的量化方法、為何選 SLO 而非 100%</li>
<li>Postmortem 文化：blameless / action items / 行動追蹤的閉環設計</li>
<li>Toil 量化：把運維工作變成可預算的工程資產</li>
<li><a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 與 burnout：值班輪值、shadow / primary 結構、心理安全</li>
<li><a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> review：服務上線前的 SRE 接管門檻</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SRE Book Ch.1-4</td>
          <td>概念基礎、為何 SLO、為何 50/50</td>
      </tr>
      <tr>
          <td>Postmortem Culture</td>
          <td>blameless 操作化、action items 追蹤</td>
      </tr>
      <tr>
          <td>Toil &amp; Engineering Time</td>
          <td>量化 toil、長期投資工程的政策</td>
      </tr>
      <tr>
          <td>Hierarchy of Reliability</td>
          <td>Monitoring → IR → PIR → Testing → Capacity → Dev → Product</td>
      </tr>
      <tr>
          <td>Embedded SRE / Consulting</td>
          <td>SRE 介入服務的多種模式</td>
      </tr>
  </tbody>
</table>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">G1</a></td>
          <td>Error Budget 與 Release Gating</td>
          <td>把 SLO 消耗量轉成放行、限速與凍結決策</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2</a></td>
          <td>Postmortem Closure 治理</td>
          <td>把事故改進項變成可追蹤、可驗證的治理節奏</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/" data-link-title="Google：Toil Budget 與 Automation 投資政策" data-link-desc="把 toil 從感受問題轉成預算問題：用時間配比與自動化回報機制，避免 on-call 壓力長期侵蝕可靠性工程。">G3</a></td>
          <td>Toil Budget 投資政策</td>
          <td>把手動運維工作轉成可預算、可回寫的工程投資</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Google 這個案例在講的是可靠性如何變成一套可操作的工程制度，而不是單一工具或單一事故。讀者先抓到 SLI / SLO、error budget、postmortem 與 toil 這幾個原語各自負責什麼，再把它們組成一條可執行的可靠性路徑。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當服務健康開始波動時，先看 SLO 是否真的被消耗，再看監控與告警是否能對應到使用者體感。當 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 壓力升高時，重點在團隊是否把重複性工作轉成可預算的工程投資，個人技巧層面的改善幫助有限。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否用一句話說明每個 SLI 對應的使用者行為</li>
<li>能否從 postmortem 找到明確 owner 與完成條件</li>
<li>能否把 toil 量化成可排程的工程時間</li>
<li>能否把監控、測試、容量、開發與產品決策串成同一條路由</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Google 提供的是可靠性的語言層，其他案例提供的是具體場景層。當讀者先懂 SLI / SLO 與 postmortem 這組原語，再看 Honeycomb 的 burn rate、Atlassian 的復原節奏或 GitHub 的 status communication，就能把抽象制度接到實際事故上。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>SLO 與 error budget 讓團隊把可靠性變成可量化的工程目標。</li>
<li>postmortem 將事故轉成可追蹤的 action items，而不是只留下檢討文字。</li>
<li>toil budget 讓重複性工作變成可預算的工程投資。</li>
<li><a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> review 讓服務在上線前先過可靠性門檻。</li>
<li><a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 與 burnout 讓值班成為組織設計問題，脫離個人耐力測試的框架。</li>
<li>hierarchy of reliability 讓 monitoring、testing、capacity、dev、product 串成一條路由。</li>
<li>blameless culture 讓檢討聚焦在系統與流程，而不是個人責任。</li>
<li>embedded SRE / consulting 讓可靠性能力可以以不同介入深度落到服務團隊。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sre.google/">sre.google</a>：Google SRE 官方資源入口，收錄 books 與主題更新。</li>
<li><a href="https://cloud.google.com/blog/products/devops-sre/the-sre-book-turns-6">The SRE book turns 6!</a>：整理 SRE Book / Workbook 與延伸資源的官方入口。</li>
<li><a href="https://cloud.google.com/blog/products/devops-sre/how-to-design-good-slos-according-to-google-sres">Adopting SRE: Standardizing your SLO design process</a>：補 SLO 設計方法與實務語境。</li>
</ul>
]]></content:encoded></item><item><title>8.1 Google：大規模微服務與索引服務</title><link>https://tarrragon.github.io/blog/go/08-case-studies/google/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/google/</guid><description>&lt;p>Google 的官方案例最適合用來理解 Go 的原始定位：這門語言的目標是解決大型工程團隊在多核心、網路、模組化與依賴管理上的問題。Google Core Data Solutions 團隊把原本的單體 C++ 索引堆疊拆成多個微服務，並把多數索引服務改寫成 Go。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/google/">Using Go at Google&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://go.dev/solutions/google/coredata">How Google’s Core Data Solutions Team Uses Go&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://go.dev/talks/2012/splash.article">Go at Google: Language Design in the Service of Software Engineering&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很適合大型服務拆分之後的邊界管理。&lt;/li>
&lt;li>built-in concurrency 對高併發索引與資料處理很重要。&lt;/li>
&lt;li>Go 的簡單語法與明確依賴，能讓大團隊維持可讀性。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/kubernetes/kubernetes">kubernetes/kubernetes&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Kubernetes 不是 Google 內部產品，但它很好地呈現了 Google 文化裡常見的 Go 工程模式：大型 codebase、明確 package 邊界、cmd 入口與大量服務協調。&lt;/p></description><content:encoded><![CDATA[<p>Google 的官方案例最適合用來理解 Go 的原始定位：這門語言的目標是解決大型工程團隊在多核心、網路、模組化與依賴管理上的問題。Google Core Data Solutions 團隊把原本的單體 C++ 索引堆疊拆成多個微服務，並把多數索引服務改寫成 Go。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/google/">Using Go at Google</a></li>
<li><a href="https://go.dev/solutions/google/coredata">How Google’s Core Data Solutions Team Uses Go</a></li>
<li><a href="https://go.dev/talks/2012/splash.article">Go at Google: Language Design in the Service of Software Engineering</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很適合大型服務拆分之後的邊界管理。</li>
<li>built-in concurrency 對高併發索引與資料處理很重要。</li>
<li>Go 的簡單語法與明確依賴，能讓大團隊維持可讀性。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/kubernetes/kubernetes">kubernetes/kubernetes</a></li>
</ul>
<p>Kubernetes 不是 Google 內部產品，但它很好地呈現了 Google 文化裡常見的 Go 工程模式：大型 codebase、明確 package 邊界、cmd 入口與大量服務協調。</p>
]]></content:encoded></item><item><title>案例：Cython 加速 Markdown 解析</title><link>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code> 的實際程式碼，展示如何用 Cython 加速文字解析。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/cython/" data-link-title="4.2 Cython：Python 語法的 C 速度" data-link-desc="使用 Cython 加速 Python 程式碼">4.2 Cython：Python 語法的 C 速度&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 使用純 Python 解析 Markdown 連結。讓我們看看核心程式碼：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MarkdownLinkChecker&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="s2">&amp;#34;&amp;#34;&amp;#34;Markdown 連結檢查器&amp;#34;&amp;#34;&amp;#34;&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"># Markdown 連結正則表達式&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"># 匹配 [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target) 格式，排除圖片 ![alt](/python-advanced/05-c-extensions/case-studies/cython-markdown/src)&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">INLINE_LINK_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&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="sa">r&lt;/span>&lt;span class="s1">&amp;#39;(?&amp;lt;!!)\[([^\]]+)\]\(([^)]+)\)&amp;#39;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結定義 [ref]: target&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;^\s*\[([^\]]+)\]:\s*(.+)$&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">MULTILINE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結使用 [text][ref]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_USE_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\[([^\]]+)\]\[([^\]]+)\]&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解析 Markdown 內容中的所有連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> content: Markdown 內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[dict]: 連結列表，每個包含 text, target, line
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 首先收集引用式連結定義&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_target&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ref_target&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 追蹤是否在程式碼區塊內&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 解析行內連結&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line_num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 檢查程式碼區塊開始/結束&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;```&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">in_code_block&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 跳過程式碼區塊內的連結&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">in_code_block&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 行內連結 [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INLINE_LINK_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結 [text][ref]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_USE_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">ref_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">links&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="效能限制">效能限制&lt;/h3>
&lt;p>純 Python 的限制：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>正則表達式呼叫開銷&lt;/strong>：每次 &lt;code>finditer()&lt;/code> 都有 Python 層級的迭代器開銷&lt;/li>
&lt;li>&lt;strong>迴圈效率不如 C&lt;/strong>：Python 的 for 迴圈涉及迭代器協議和物件建立&lt;/li>
&lt;li>&lt;strong>字串處理有額外開銷&lt;/strong>：&lt;code>split()&lt;/code>、&lt;code>strip()&lt;/code>、&lt;code>startswith()&lt;/code> 都會建立新物件&lt;/li>
&lt;li>&lt;strong>字典存取開銷&lt;/strong>：&lt;code>reference_defs[ref_name]&lt;/code> 涉及雜湊計算和物件比較&lt;/li>
&lt;/ul>
&lt;p>當處理大量 Markdown 文件（例如整個文件專案）時，這些開銷會累積成可觀的效能損失。&lt;/p>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="優化目標">優化目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>保持相同的 API&lt;/strong>：不改變 &lt;code>parse_markdown_links()&lt;/code> 的輸入輸出格式&lt;/li>
&lt;li>&lt;strong>顯著提升解析速度&lt;/strong>：目標 2-5x 加速&lt;/li>
&lt;li>&lt;strong>容易整合到現有專案&lt;/strong>：編譯後可直接替換原模組&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立-pyx-檔案">步驟 1：建立 .pyx 檔案&lt;/h4>
&lt;p>首先，建立基本的 Cython 檔案結構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cython" data-lang="cython">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># markdown_parser.pyx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="sd">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="sd">Cython accelerated Markdown link parser.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="sd">This module provides fast parsing of Markdown links,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="sd">compatible with the original Python implementation.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="sd">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="k">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="k">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c"># Compile regex patterns at module level for reuse&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="k">cdef&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="nf">INLINE_LINK_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s">r&amp;#39;(?&amp;lt;!!)\[([^\]]+)\]\(([^)]+)\)&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="k">cdef&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="nf">REFERENCE_DEF_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s">r&amp;#39;^\s*\[([^\]]+)\]:\s*(.+)$&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">MULTILINE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="k">cdef&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="nf">REFERENCE_USE_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="s">r&amp;#39;\[([^\]]+)\]\[([^\]]+)\]&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&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;strong>重點說明&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code> 的實際程式碼，展示如何用 Cython 加速文字解析。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/05-c-extensions/cython/" data-link-title="4.2 Cython：Python 語法的 C 速度" data-link-desc="使用 Cython 加速 Python 程式碼">4.2 Cython：Python 語法的 C 速度</a></li>
<li><a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 使用純 Python 解析 Markdown 連結。讓我們看看核心程式碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Markdown 連結檢查器&#34;&#34;&#34;</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"># Markdown 連結正則表達式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 匹配 [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target) 格式，排除圖片 ![alt](/python-advanced/05-c-extensions/case-studies/cython-markdown/src)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</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></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 引用式連結定義 [ref]: target</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 引用式連結使用 [text][ref]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">        解析 Markdown 內容中的所有連結
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">            content: Markdown 內容
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">            list[dict]: 連結列表，每個包含 text, target, line
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c1"># 首先收集引用式連結定義</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="c1"># 追蹤是否在程式碼區塊內</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="c1"># 解析行內連結</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="c1"># 檢查程式碼區塊開始/結束</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="c1"># 跳過程式碼區塊內的連結</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">            <span class="c1"># 行內連結 [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="c1"># 引用式連結 [text][ref]</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="k">return</span> <span class="n">links</span></span></span></code></pre></div><h3 id="效能限制">效能限制</h3>
<p>純 Python 的限制：</p>
<ul>
<li><strong>正則表達式呼叫開銷</strong>：每次 <code>finditer()</code> 都有 Python 層級的迭代器開銷</li>
<li><strong>迴圈效率不如 C</strong>：Python 的 for 迴圈涉及迭代器協議和物件建立</li>
<li><strong>字串處理有額外開銷</strong>：<code>split()</code>、<code>strip()</code>、<code>startswith()</code> 都會建立新物件</li>
<li><strong>字典存取開銷</strong>：<code>reference_defs[ref_name]</code> 涉及雜湊計算和物件比較</li>
</ul>
<p>當處理大量 Markdown 文件（例如整個文件專案）時，這些開銷會累積成可觀的效能損失。</p>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="優化目標">優化目標</h3>
<ol>
<li><strong>保持相同的 API</strong>：不改變 <code>parse_markdown_links()</code> 的輸入輸出格式</li>
<li><strong>顯著提升解析速度</strong>：目標 2-5x 加速</li>
<li><strong>容易整合到現有專案</strong>：編譯後可直接替換原模組</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立-pyx-檔案">步驟 1：建立 .pyx 檔案</h4>
<p>首先，建立基本的 Cython 檔案結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># markdown_parser.pyx</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="sd">Cython accelerated Markdown link parser.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">This module provides fast parsing of Markdown links,
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">compatible with the original Python implementation.
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">from</span> <span class="nn">typing</span> <span class="k">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># Compile regex patterns at module level for reuse</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s">r&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s">r&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="s">r&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li>使用 <code>cdef object</code> 宣告正則表達式物件，讓 Cython 知道這些是 Python 物件</li>
<li>將正則表達式編譯放在模組層級，避免重複編譯</li>
<li>保留 docstring 和 type hints 以維護可讀性</li>
</ul>
<h4 id="步驟-2添加型別宣告">步驟 2：添加型別宣告</h4>
<p>為關鍵變數添加 C 型別宣告：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># markdown_parser.pyx (continued)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">cdef</span> <span class="k">class</span> <span class="nf">LinkInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">    C-level struct to hold link information.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">    Faster than Python dict for internal operations.
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">str</span> <span class="nf">text</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">str</span> <span class="nf">target</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">int</span> <span class="nf">line</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">str</span> <span class="n">text</span><span class="p">,</span> <span class="nb">str</span> <span class="n">target</span><span class="p">,</span> <span class="nb">int</span> <span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">target</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="sd">&#34;&#34;&#34;Convert to dictionary for API compatibility.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="s">&#34;text&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s">&#34;target&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="s">&#34;line&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">line</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">cdef</span> <span class="kt">bint</span> <span class="nf">is_code_fence</span><span class="p">(</span><span class="nb">str</span> <span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="sd">    Check if line is a code fence marker.
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="sd">    cdef function: only callable from Cython, fastest.
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">cdef</span> <span class="kt">str</span> <span class="nf">stripped</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="n">stripped</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">&#34;```&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="n">stripped</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">&#34;~~~&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li><code>cdef class LinkInfo</code>：使用 Cython 的擴展類別，內部存取比 Python dict 快</li>
<li><code>cdef public</code>：讓屬性可以從 Python 存取，同時保持 C 層級效率</li>
<li><code>cdef bint</code>：使用 C 的布林型別（0 或 1），比 Python 的 <code>bool</code> 快</li>
<li><code>cdef</code> 函式：只能從 Cython 呼叫，沒有 Python 呼叫開銷</li>
</ul>
<h4 id="步驟-3優化迴圈">步驟 3：優化迴圈</h4>
<p>使用 Cython 優化主要的解析迴圈：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># markdown_parser.pyx (continued)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">cdef</span> <span class="kt">list</span> <span class="nf">_parse_inline_links</span><span class="p">(</span><span class="nb">list</span> <span class="n">lines</span><span class="p">,</span> <span class="nb">dict</span> <span class="n">reference_defs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">    Parse inline and reference links from lines.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd">    Internal function with optimized loop.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nb">list</span> <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nb">int</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nb">int</span> <span class="n">total_lines</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">bint</span> <span class="n">in_code_block</span> <span class="o">=</span> <span class="bp">False</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="nb">str</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_name</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">object</span> <span class="n">match</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">for</span> <span class="n">line_num</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">total_lines</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">line</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="n">line_num</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c"># Check code fence</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">if</span> <span class="n">is_code_fence</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="c"># Parse inline links [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">line_num</span> <span class="o">+</span> <span class="mf">1</span>  <span class="c"># 1-indexed</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c"># Parse reference links [text][ref]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                    <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                    <span class="n">line_num</span> <span class="o">+</span> <span class="mf">1</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">cdef</span> <span class="kt">dict</span> <span class="nf">_collect_reference_defs</span><span class="p">(</span><span class="nb">str</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="sd">    Collect reference link definitions from content.
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="sd">    Returns dict mapping ref_name -&gt; target.
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="nb">dict</span> <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="nb">object</span> <span class="n">match</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_name</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="n">ref_name</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="n">ref_target</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="k">return</span> <span class="n">reference_defs</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li><code>cdef list</code>、<code>cdef dict</code>：明確宣告容器型別，減少型別檢查開銷</li>
<li><code>cdef int line_num</code>：使用 C 整數進行迴圈計數</li>
<li><code>cdef bint in_code_block</code>：使用 C 布林型別追蹤狀態</li>
<li>將功能分解成多個 <code>cdef</code> 函式，每個函式專注單一職責</li>
</ul>
<h4 id="步驟-4建立公開-api">步驟 4：建立公開 API</h4>
<p>使用 <code>cpdef</code> 或 <code>def</code> 建立可從 Python 呼叫的公開介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># markdown_parser.pyx (continued)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">cpdef</span> <span class="kt">list</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="nb">str</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">    Parse all links from Markdown content.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd">    This is the main public API, compatible with the original
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sd">    Python implementation.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="sd">    Args:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="sd">        content: Markdown content string
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="sd">    Returns:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sd">        List of dicts with &#39;text&#39;, &#39;target&#39;, &#39;line&#39; keys
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="nb">list</span> <span class="n">lines</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="nb">dict</span> <span class="n">reference_defs</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="nb">list</span> <span class="n">link_infos</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="nb">list</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">LinkInfo</span> <span class="n">info</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c"># Split content into lines</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;</span><span class="se">\n</span><span class="s">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c"># Collect reference definitions</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">reference_defs</span> <span class="o">=</span> <span class="n">_collect_reference_defs</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c"># Parse all links</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">link_infos</span> <span class="o">=</span> <span class="n">_parse_inline_links</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">reference_defs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c"># Convert to dict format for API compatibility</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="p">[</span><span class="n">info</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">link_infos</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">def</span> <span class="nf">parse_markdown_links_py</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="sd">    Python-compatible wrapper with type hints.
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="sd">    Identical to parse_markdown_links but with explicit
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="sd">    Python type annotations for better IDE support.
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">return</span> <span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li><code>cpdef</code>：同時產生 Python 和 C 版本，從 Python 呼叫時用 Python 版本，從 Cython 呼叫時用 C 版本</li>
<li>保持 API 相容性：回傳格式與原始 Python 版本完全相同</li>
<li>提供 <code>_py</code> 版本：帶有完整型別提示，改善 IDE 支援</li>
</ul>
<h4 id="步驟-5建立-setuppy">步驟 5：建立 setup.py</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># setup.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Build script for Cython markdown parser.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">Usage:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    python setup.py build_ext --inplace
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">Or for development with automatic rebuild:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    pip install -e .
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">Extension</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">Cython.Build</span> <span class="kn">import</span> <span class="n">cythonize</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">extensions</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">Extension</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;markdown_parser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">sources</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;markdown_parser.pyx&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># Optional: add compiler directives for optimization</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># extra_compile_args=[&#34;-O3&#34;],</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">setup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;markdown_parser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">version</span><span class="o">=</span><span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Cython accelerated Markdown link parser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">ext_modules</span><span class="o">=</span><span class="n">cythonize</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">extensions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">compiler_directives</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="s2">&#34;language_level&#34;</span><span class="p">:</span> <span class="s2">&#34;3&#34;</span><span class="p">,</span>      <span class="c1"># Python 3 syntax</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="s2">&#34;boundscheck&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>       <span class="c1"># Disable bounds checking</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="s2">&#34;wraparound&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>        <span class="c1"># Disable negative indexing</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="s2">&#34;cdivision&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>          <span class="c1"># Use C division semantics</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">annotate</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>  <span class="c1"># Generate HTML annotation file</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">zip_safe</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p><strong>編譯指令說明</strong>：</p>
<table>
  <thead>
      <tr>
          <th>指令</th>
          <th>說明</th>
          <th>效能影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>language_level=3</code></td>
          <td>使用 Python 3 語法</td>
          <td>無</td>
      </tr>
      <tr>
          <td><code>boundscheck=False</code></td>
          <td>停用陣列邊界檢查</td>
          <td>加速 5-10%</td>
      </tr>
      <tr>
          <td><code>wraparound=False</code></td>
          <td>停用負數索引支援</td>
          <td>加速 2-5%</td>
      </tr>
      <tr>
          <td><code>cdivision=True</code></td>
          <td>使用 C 的除法（不檢查除以零）</td>
          <td>加速除法運算</td>
      </tr>
      <tr>
          <td><code>annotate=True</code></td>
          <td>產生 HTML 註解報告</td>
          <td>僅開發時使用</td>
      </tr>
  </tbody>
</table>
<h3 id="完整程式碼">完整程式碼</h3>
<p>將以上所有部分整合成完整的 <code>.pyx</code> 檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c"># markdown_parser.pyx</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="sd">Cython accelerated Markdown link parser.
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="sd">This module provides fast parsing of Markdown links,
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="sd">compatible with the original Python implementation.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="sd">Build:
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="sd">    python setup.py build_ext --inplace
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="sd">Usage:
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="sd">    from markdown_parser import parse_markdown_links
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="sd">    links = parse_markdown_links(markdown_content)
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="sd">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">from</span> <span class="nn">typing</span> <span class="k">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c"># Compiled regex patterns (module level for reuse)</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="s">r&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s">r&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="k">cdef</span> <span class="kt">object</span> <span class="nf">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="s">r&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="c"># C-level data structures</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="k">cdef</span> <span class="k">class</span> <span class="nf">LinkInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="sd">    C-level struct to hold link information.
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="sd">    Faster than Python dict for internal operations.
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">str</span> <span class="nf">text</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">str</span> <span class="nf">target</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="k">cdef</span> <span class="kr">public</span> <span class="kt">int</span> <span class="nf">line</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">str</span> <span class="n">text</span><span class="p">,</span> <span class="nb">str</span> <span class="n">target</span><span class="p">,</span> <span class="nb">int</span> <span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">target</span> <span class="o">=</span> <span class="n">target</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="sd">&#34;&#34;&#34;Convert to dictionary for API compatibility.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="s">&#34;text&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">            <span class="s">&#34;target&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">target</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="s">&#34;line&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">line</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="k">def</span> <span class="nf">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">return</span> <span class="n">f</span><span class="s">&#34;LinkInfo(text={self.text!r}, target={self.target!r}, line={self.line})&#34;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="c"># Internal helper functions (cdef = C-only, fastest)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="k">cdef</span> <span class="kt">bint</span> <span class="nf">is_code_fence</span><span class="p">(</span><span class="nb">str</span> <span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="sd">    Check if line is a code fence marker.
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="k">cdef</span> <span class="kt">str</span> <span class="nf">stripped</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="k">return</span> <span class="n">stripped</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">&#34;```&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="n">stripped</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">&#34;~~~&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="k">cdef</span> <span class="kt">dict</span> <span class="nf">_collect_reference_defs</span><span class="p">(</span><span class="nb">str</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="sd">    Collect reference link definitions from content.
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="nb">dict</span> <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="nb">object</span> <span class="n">match</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_name</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">ref_name</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="n">ref_target</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">return</span> <span class="n">reference_defs</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="k">cdef</span> <span class="kt">list</span> <span class="nf">_parse_inline_links</span><span class="p">(</span><span class="nb">list</span> <span class="n">lines</span><span class="p">,</span> <span class="nb">dict</span> <span class="n">reference_defs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="sd">    Parse inline and reference links from lines.
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="nb">list</span> <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="nb">int</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="nb">int</span> <span class="n">total_lines</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">bint</span> <span class="n">in_code_block</span> <span class="o">=</span> <span class="bp">False</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="nb">str</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="nb">str</span> <span class="n">ref_name</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="nb">object</span> <span class="n">match</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="k">for</span> <span class="n">line_num</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">total_lines</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">line</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="n">line_num</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="c"># Check code fence</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">if</span> <span class="n">is_code_fence</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="c"># Parse inline links [text](/python-advanced/05-c-extensions/case-studies/cython-markdown/target)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">                <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">                <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">                <span class="n">line_num</span> <span class="o">+</span> <span class="mf">1</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="c"># Parse reference links [text][ref]</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">            <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">                    <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mf">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">                    <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">                    <span class="n">line_num</span> <span class="o">+</span> <span class="mf">1</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">
</span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c"># Public API (cpdef = callable from both Python and Cython)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="k">cpdef</span> <span class="kt">list</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="nb">str</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="sd">    Parse all links from Markdown content.
</span></span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="sd">    Args:
</span></span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="sd">        content: Markdown content string
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="sd">    Returns:
</span></span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="sd">        List of dicts with &#39;text&#39;, &#39;target&#39;, &#39;line&#39; keys
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="sd">    Example:
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="sd">        &gt;&gt;&gt; content = &#34;[Click here](https://example.com)&#34;
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="sd">        &gt;&gt;&gt; links = parse_markdown_links(content)
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="sd">        &gt;&gt;&gt; links[0][&#39;target&#39;]
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="sd">        &#39;https://example.com&#39;
</span></span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="nb">list</span> <span class="n">lines</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="nb">dict</span> <span class="n">reference_defs</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="nb">list</span> <span class="n">link_infos</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">        <span class="nb">list</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">        <span class="n">LinkInfo</span> <span class="n">info</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;</span><span class="se">\n</span><span class="s">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">    <span class="n">reference_defs</span> <span class="o">=</span> <span class="n">_collect_reference_defs</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="n">link_infos</span> <span class="o">=</span> <span class="n">_parse_inline_links</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">reference_defs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="p">[</span><span class="n">info</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">link_infos</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="c"># Python-compatible wrapper with full type hints</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="k">def</span> <span class="nf">parse_markdown_links_py</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="sd">    Python-compatible wrapper with type hints.
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="sd">    Identical to parse_markdown_links but with explicit
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="sd">    Python type annotations for better IDE support.
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">return</span> <span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">
</span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="c"># Optional: Expose LinkInfo class for advanced usage</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="c"># ============================================================</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="k">def</span> <span class="nf">parse_markdown_links_fast</span><span class="p">(</span><span class="nb">str</span> <span class="n">content</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="sd">    Parse links and return LinkInfo objects directly.
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="sd">    Faster than parse_markdown_links() as it skips
</span></span></span><span class="line"><span class="ln">190</span><span class="cl"><span class="sd">    the dict conversion step.
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="sd">    Returns:
</span></span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="sd">        List of LinkInfo objects
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="sd">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="k">cdef</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="nb">list</span> <span class="n">lines</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="nb">dict</span> <span class="n">reference_defs</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;</span><span class="se">\n</span><span class="s">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="n">reference_defs</span> <span class="o">=</span> <span class="n">_collect_reference_defs</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">    <span class="k">return</span> <span class="n">_parse_inline_links</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">reference_defs</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能比較">效能比較</h3>
<p>建立效能測試腳本來比較純 Python 和 Cython 版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1"># benchmark.py</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Performance comparison between Python and Cython implementations.
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">Usage:
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    # First, build the Cython module
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    python setup.py build_ext --inplace
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    # Then run benchmark
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    python benchmark.py
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">import</span> <span class="nn">statistics</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="c1"># Pure Python implementation (inline for comparison)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="k">class</span> <span class="nc">PythonMarkdownParser</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Original pure Python implementation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="k">def</span> <span class="nf">generate_test_content</span><span class="p">(</span><span class="n">num_lines</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">links_per_100_lines</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate test Markdown content with specified characteristics.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_lines</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="p">(</span><span class="mi">100</span> <span class="o">//</span> <span class="n">links_per_100_lines</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="c1"># Add an inline link</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check out [Link </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](https://example.com/page</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">) for details.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">50</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">            <span class="c1"># Add a code block</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;```python&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;# This is code, links here [should](/python-advanced/05-c-extensions/case-studies/cython-markdown/be/ignored)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="c1"># Regular text</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;This is line </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2"> with some regular text content.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">return</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Run benchmark and return statistics.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="c1"># Warmup</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">func</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="c1"># Actual benchmark</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="s2">&#34;mean&#34;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>  <span class="c1"># Convert to ms</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="s2">&#34;stdev&#34;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">stdev</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="s2">&#34;min&#34;</span><span class="p">:</span> <span class="nb">min</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="s2">&#34;max&#34;</span><span class="p">:</span> <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="s2">&#34;links_found&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Markdown Link Parser Benchmark&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="c1"># Test different content sizes</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="n">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">5000</span><span class="p">,</span> <span class="mi">10000</span><span class="p">,</span> <span class="mi">50000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">python_parser</span> <span class="o">=</span> <span class="n">PythonMarkdownParser</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="c1"># Try to import Cython version</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="kn">from</span> <span class="nn">markdown_parser</span> <span class="kn">import</span> <span class="n">parse_markdown_links</span> <span class="k">as</span> <span class="n">cython_parse</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">has_cython</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Warning: Cython module not found.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Run &#39;python setup.py build_ext --inplace&#39; first.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="n">has_cython</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">sizes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">--- Content size: </span><span class="si">{</span><span class="n">size</span><span class="si">}</span><span class="s2"> lines ---&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="n">size</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="c1"># Python benchmark</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">py_result</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">python_parser</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Python:  </span><span class="si">{</span><span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> ms (+/- </span><span class="si">{</span><span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;stdev&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> ms)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;         Found </span><span class="si">{</span><span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;links_found&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> links&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="c1"># Cython benchmark (if available)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="n">has_cython</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">cy_result</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">cython_parse</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">            <span class="n">speedup</span> <span class="o">=</span> <span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span> <span class="o">/</span> <span class="n">cy_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cython:  </span><span class="si">{</span><span class="n">cy_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> ms (+/- </span><span class="si">{</span><span class="n">cy_result</span><span class="p">[</span><span class="s1">&#39;stdev&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> ms)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;         Speedup: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h3 id="預期結果">預期結果</h3>
<p>執行效能測試後，預期會看到類似以下的結果：</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">============================================================
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Markdown Link Parser Benchmark
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">--- Content size: 1000 lines ---
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Python:  0.523 ms (+/- 0.031 ms)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">         Found 100 links
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Cython:  0.198 ms (+/- 0.012 ms)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">         Speedup: 2.64x
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">--- Content size: 5000 lines ---
</span></span><span class="line"><span class="ln">12</span><span class="cl">Python:  2.617 ms (+/- 0.089 ms)
</span></span><span class="line"><span class="ln">13</span><span class="cl">         Found 500 links
</span></span><span class="line"><span class="ln">14</span><span class="cl">Cython:  0.892 ms (+/- 0.045 ms)
</span></span><span class="line"><span class="ln">15</span><span class="cl">         Speedup: 2.93x
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">--- Content size: 10000 lines ---
</span></span><span class="line"><span class="ln">18</span><span class="cl">Python:  5.234 ms (+/- 0.156 ms)
</span></span><span class="line"><span class="ln">19</span><span class="cl">         Found 1000 links
</span></span><span class="line"><span class="ln">20</span><span class="cl">Cython:  1.712 ms (+/- 0.078 ms)
</span></span><span class="line"><span class="ln">21</span><span class="cl">         Speedup: 3.06x
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">--- Content size: 50000 lines ---
</span></span><span class="line"><span class="ln">24</span><span class="cl">Python:  26.18 ms (+/- 0.823 ms)
</span></span><span class="line"><span class="ln">25</span><span class="cl">         Found 5000 links
</span></span><span class="line"><span class="ln">26</span><span class="cl">Cython:  7.89 ms (+/- 0.312 ms)
</span></span><span class="line"><span class="ln">27</span><span class="cl">         Speedup: 3.32x
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">============================================================</span></span></code></pre></div><p><strong>結果分析</strong>：</p>
<table>
  <thead>
      <tr>
          <th>內容大小</th>
          <th>Python</th>
          <th>Cython</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1,000 行</td>
          <td>0.52 ms</td>
          <td>0.20 ms</td>
          <td>2.6x</td>
      </tr>
      <tr>
          <td>5,000 行</td>
          <td>2.62 ms</td>
          <td>0.89 ms</td>
          <td>2.9x</td>
      </tr>
      <tr>
          <td>10,000 行</td>
          <td>5.23 ms</td>
          <td>1.71 ms</td>
          <td>3.1x</td>
      </tr>
      <tr>
          <td>50,000 行</td>
          <td>26.2 ms</td>
          <td>7.89 ms</td>
          <td>3.3x</td>
      </tr>
  </tbody>
</table>
<p>觀察：</p>
<ul>
<li>加速比隨著資料量增加而提高</li>
<li>主要效能提升來自迴圈優化和型別化變數</li>
<li>正則表達式仍然是瓶頸（Cython 無法加速 <code>re</code> 模組本身）</li>
</ul>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>純 Python</th>
          <th>Cython</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>開發速度</strong></td>
          <td>快，即寫即用</td>
          <td>中，需要編譯步驟</td>
      </tr>
      <tr>
          <td><strong>執行速度</strong></td>
          <td>基準</td>
          <td>2-5x 加速</td>
      </tr>
      <tr>
          <td><strong>除錯難度</strong></td>
          <td>低，標準 Python 工具</td>
          <td>中，需要看生成的 C 碼</td>
      </tr>
      <tr>
          <td><strong>部署複雜度</strong></td>
          <td>簡單，純 Python</td>
          <td>需要編譯環境或預編譯 wheel</td>
      </tr>
      <tr>
          <td><strong>可維護性</strong></td>
          <td>高</td>
          <td>中，需要了解 Cython 語法</td>
      </tr>
      <tr>
          <td><strong>IDE 支援</strong></td>
          <td>完整</td>
          <td>部分（.pyx 支援有限）</td>
      </tr>
      <tr>
          <td><strong>跨平台</strong></td>
          <td>天生跨平台</td>
          <td>需要為每個平台編譯</td>
      </tr>
  </tbody>
</table>
<h2 id="進階優化使用-c-正則表達式">進階優化：使用 C 正則表達式</h2>
<p>如果需要更高的效能，可以考慮使用 C 語言的正則表達式庫。以下是使用 PCRE2 的範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cython" data-lang="cython"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># advanced_parser.pyx</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="sd">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="sd">Advanced parser using PCRE2 C library for maximum performance.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">Requires: libpcre2-dev (Ubuntu) or pcre2 (macOS Homebrew)
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">cdef</span> <span class="kr">extern</span> <span class="k">from</span> <span class="s">&#34;pcre2.h&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c"># PCRE2 declarations...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># This is an advanced topic, see PCRE2 documentation for details</span></span></span></code></pre></div><p>不過，對於大多數使用情境，Python 的 <code>re</code> 模組配合 Cython 優化的迴圈已經足夠。</p>
<h2 id="什麼時候該用-cython">什麼時候該用 Cython？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li>熱點程式碼已經用 profiler 確認</li>
<li>需要 2x 以上的效能提升</li>
<li>程式碼相對穩定，不常變動</li>
<li>團隊有能力維護 Cython 程式碼</li>
<li>可以接受編譯步驟</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li>效能瓶頸在 I/O（網路、磁碟）</li>
<li>程式碼還在頻繁迭代中</li>
<li>跨平台部署且沒有 CI/CD 支援</li>
<li>團隊對 C 語言不熟悉</li>
<li>效能提升不到 2x</li>
</ul>
<h3 id="替代方案考量">替代方案考量</h3>





<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">如果 Cython 不適合你的情境，考慮：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">1. PyPy
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - 無需修改程式碼
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   - JIT 編譯帶來 5-10x 加速
</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></span><span class="line"><span class="ln"> 8</span><span class="cl">2. Numba
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - 針對數值計算優化
</span></span><span class="line"><span class="ln">10</span><span class="cl">   - 使用裝飾器即可加速
</span></span><span class="line"><span class="ln">11</span><span class="cl">   - 但僅支援部分 Python 語法
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">3. 演算法優化
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 先檢查是否有更好的演算法
</span></span><span class="line"><span class="ln">15</span><span class="cl">   - 減少不必要的記憶體分配
</span></span><span class="line"><span class="ln">16</span><span class="cl">   - 使用更高效的資料結構</span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<p>將以下純 Python 函式轉換為 Cython：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># exercise_1.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">count_words</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Count word frequencies in text.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">counts</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">word</span> <span class="o">=</span> <span class="n">word</span><span class="o">.</span><span class="n">strip</span><span class="p">(</span><span class="s1">&#39;.,!?;:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">if</span> <span class="n">word</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">counts</span><span class="p">[</span><span class="n">word</span><span class="p">]</span> <span class="o">=</span> <span class="n">counts</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">word</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">counts</span></span></span></code></pre></div><p>提示：</p>
<ol>
<li>建立 <code>exercise_1.pyx</code></li>
<li>為 <code>counts</code> 變數添加 <code>cdef dict</code> 宣告</li>
<li>為迴圈變數添加適當的型別宣告</li>
<li>考慮使用 <code>cdef</code> 輔助函式處理字串清理</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<p>使用 cProfile 驗證 Cython 加速效果：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># exercise_2.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">profile_parsers</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Profile both Python and Cython parsers.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="kn">from</span> <span class="nn">markdown_parser</span> <span class="kn">import</span> <span class="n">parse_markdown_links</span> <span class="k">as</span> <span class="n">cython_parse</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">python_parser</span> <span class="o">=</span> <span class="n">PythonMarkdownParser</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># Profile Python version</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Python Version ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">cProfile</span><span class="o">.</span><span class="n">runctx</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s1">&#39;for _ in range(100): python_parser.parse_markdown_links(content)&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">globals</span><span class="p">(),</span> <span class="nb">locals</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s1">&#39;python_stats&#39;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="s1">&#39;python_stats&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">strip_dirs</span><span class="p">()</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="s1">&#39;cumulative&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># Profile Cython version</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== Cython Version ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">cProfile</span><span class="o">.</span><span class="n">runctx</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s1">&#39;for _ in range(100): cython_parse(content)&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="nb">globals</span><span class="p">(),</span> <span class="nb">locals</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s1">&#39;cython_stats&#39;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="s1">&#39;cython_stats&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">strip_dirs</span><span class="p">()</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="s1">&#39;cumulative&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<p>比較不同型別宣告策略的效能影響：</p>
<ol>
<li><strong>無型別宣告</strong>：將 <code>.pyx</code> 當作純 Python 編譯</li>
<li><strong>部分型別宣告</strong>：只為迴圈變數添加型別</li>
<li><strong>完整型別宣告</strong>：為所有變數添加型別</li>
<li><strong>使用 LinkInfo 類別</strong> vs <strong>使用 dict</strong></li>
</ol>
<p>記錄每種策略的效能，並分析哪些優化帶來最大效益。</p>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://cython.readthedocs.io/">Cython 官方文件</a></li>
<li><a href="https://cython.readthedocs.io/en/latest/src/tutorial/cython_tutorial.html">Cython 最佳實踐</a></li>
<li><a href="https://cython.readthedocs.io/en/latest/src/userguide/numpy_tutorial.html">Cython 與 NumPy 整合</a></li>
<li><a href="https://cython.readthedocs.io/en/latest/src/userguide/debugging.html">Cython 產生的 C 程式碼分析</a></li>
</ul>
<hr>
<p><em>返回：<a href="/blog/python-advanced/05-c-extensions/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的 C 擴展案例">案例研究</a></em>
<em>返回：<a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組四：用 C 擴展 Python</a></em></p>
]]></content:encoded></item><item><title>案例：PyO3 文字解析</title><link>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code> 的實際程式碼，展示如何用 PyO3 和 Rust 實現高效能的文字解析器。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">模組五：用 Rust 擴展 Python&lt;/a>&lt;/li>
&lt;li>Rust 基礎語法&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/" data-link-title="案例：Cython 加速 Markdown 解析" data-link-desc="用 Cython 加速 Markdown 連結解析器，比較純 Python 與 Cython 的效能差異">5.1 Cython 加速&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 使用純 Python 的正則表達式解析 Markdown 連結：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MarkdownLinkChecker&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="s2">&amp;#34;&amp;#34;&amp;#34;Markdown 連結檢查器&amp;#34;&amp;#34;&amp;#34;&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"># Markdown 連結正則表達式&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"># 匹配 [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target) 格式，排除圖片 ![alt](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/src)&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">INLINE_LINK_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&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="sa">r&lt;/span>&lt;span class="s1">&amp;#39;(?&amp;lt;!!)\[([^\]]+)\]\(([^)]+)\)&amp;#39;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結定義 [ref]: target&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;^\s*\[([^\]]+)\]:\s*(.+)$&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">MULTILINE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結使用 [text][ref]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_USE_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\[([^\]]+)\]\[([^\]]+)\]&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解析 Markdown 內容中的所有連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> content: Markdown 內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[dict]: 連結列表，每個包含 text, target, line
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 首先收集引用式連結定義&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_target&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ref_target&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 追蹤是否在程式碼區塊內&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 解析行內連結&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line_num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 檢查程式碼區塊開始/結束&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;```&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">in_code_block&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 跳過程式碼區塊內的連結&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">in_code_block&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 行內連結 [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INLINE_LINK_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 引用式連結 [text][ref]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_USE_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">ref_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">links&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式碼的核心瓶頸：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>正則表達式解析&lt;/strong>：Python 的 &lt;code>re&lt;/code> 模組效能有限&lt;/li>
&lt;li>&lt;strong>字串分割與迭代&lt;/strong>：大量的記憶體配置&lt;/li>
&lt;li>&lt;strong>字典操作&lt;/strong>：每個連結都建立新字典&lt;/li>
&lt;/ol>
&lt;h3 id="為什麼選擇-rust">為什麼選擇 Rust？&lt;/h3>
&lt;p>相比 Cython：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Cython&lt;/th>
 &lt;th>Rust (PyO3)&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>記憶體安全&lt;/td>
 &lt;td>依賴 GC&lt;/td>
 &lt;td>編譯時保證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>正則表達式&lt;/td>
 &lt;td>仍用 Python re&lt;/td>
 &lt;td>原生 regex crate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>錯誤處理&lt;/td>
 &lt;td>例外機制&lt;/td>
 &lt;td>Result 類型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>多執行緒&lt;/td>
 &lt;td>受 GIL 限制&lt;/td>
 &lt;td>可完全繞過 GIL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生態系統&lt;/td>
 &lt;td>有限&lt;/td>
 &lt;td>豐富的 Cargo 生態&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>用 Rust 重寫核心解析邏輯&lt;/li>
&lt;li>保持 Python API 相容&lt;/li>
&lt;li>實現顯著的效能提升&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立-rust-專案結構">步驟 1：建立 Rust 專案結構&lt;/h4>
&lt;p>首先，使用 Maturin 建立新專案：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code> 的實際程式碼，展示如何用 PyO3 和 Rust 實現高效能的文字解析器。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">模組五：用 Rust 擴展 Python</a></li>
<li>Rust 基礎語法</li>
<li><a href="/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/" data-link-title="案例：Cython 加速 Markdown 解析" data-link-desc="用 Cython 加速 Markdown 連結解析器，比較純 Python 與 Cython 的效能差異">5.1 Cython 加速</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 使用純 Python 的正則表達式解析 Markdown 連結：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Markdown 連結檢查器&#34;&#34;&#34;</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"># Markdown 連結正則表達式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 匹配 [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target) 格式，排除圖片 ![alt](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/src)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</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></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 引用式連結定義 [ref]: target</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 引用式連結使用 [text][ref]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">        解析 Markdown 內容中的所有連結
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">            content: Markdown 內容
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">            list[dict]: 連結列表，每個包含 text, target, line
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c1"># 首先收集引用式連結定義</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="c1"># 追蹤是否在程式碼區塊內</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="c1"># 解析行內連結</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="c1"># 檢查程式碼區塊開始/結束</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="c1"># 跳過程式碼區塊內的連結</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">            <span class="c1"># 行內連結 [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="c1"># 引用式連結 [text][ref]</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="k">return</span> <span class="n">links</span></span></span></code></pre></div><p>這段程式碼的核心瓶頸：</p>
<ol>
<li><strong>正則表達式解析</strong>：Python 的 <code>re</code> 模組效能有限</li>
<li><strong>字串分割與迭代</strong>：大量的記憶體配置</li>
<li><strong>字典操作</strong>：每個連結都建立新字典</li>
</ol>
<h3 id="為什麼選擇-rust">為什麼選擇 Rust？</h3>
<p>相比 Cython：</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Cython</th>
          <th>Rust (PyO3)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體安全</td>
          <td>依賴 GC</td>
          <td>編譯時保證</td>
      </tr>
      <tr>
          <td>正則表達式</td>
          <td>仍用 Python re</td>
          <td>原生 regex crate</td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td>例外機制</td>
          <td>Result 類型</td>
      </tr>
      <tr>
          <td>多執行緒</td>
          <td>受 GIL 限制</td>
          <td>可完全繞過 GIL</td>
      </tr>
      <tr>
          <td>生態系統</td>
          <td>有限</td>
          <td>豐富的 Cargo 生態</td>
      </tr>
  </tbody>
</table>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li>用 Rust 重寫核心解析邏輯</li>
<li>保持 Python API 相容</li>
<li>實現顯著的效能提升</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立-rust-專案結構">步驟 1：建立 Rust 專案結構</h4>
<p>首先，使用 Maturin 建立新專案：</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"><span class="c1"># 安裝 maturin（如果尚未安裝）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">pip install maturin
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 建立新專案</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">mkdir markdown_parser_rs
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nb">cd</span> markdown_parser_rs
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 初始化 maturin 專案（選擇 pyo3 作為綁定）</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">maturin init --bindings pyo3
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 專案結構如下：</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># markdown_parser_rs/</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># ├── Cargo.toml</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># ├── pyproject.toml</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># └── src/</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">#     └── lib.rs</span></span></span></code></pre></div><p>接著，編輯 <code>Cargo.toml</code> 加入必要的依賴：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">package</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;markdown_parser_rs&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">edition</span> <span class="p">=</span> <span class="s2">&#34;2021&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">lib</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;markdown_parser_rs&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">crate-type</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;cdylib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">pyo3</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.22&#34;</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;extension-module&#34;</span><span class="p">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">regex</span> <span class="p">=</span> <span class="s2">&#34;1.11&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">once_cell</span> <span class="p">=</span> <span class="s2">&#34;1.20&#34;</span></span></span></code></pre></div><h4 id="步驟-2實作-rust-解析函式">步驟 2：實作 Rust 解析函式</h4>
<p>先定義核心的資料結構和解析邏輯：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/lib.rs
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="n">Regex</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="sd">/// Represents a parsed markdown link
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd"></span><span class="cp">#[derive(Debug, Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">text</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">target</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">line</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w"></span><span class="c1">// Pre-compiled regex patterns (compile once, use many times)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">INLINE_LINK_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="c1">// Match [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target), excluding images ![alt](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/src)
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">REFERENCE_DEF_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">    </span><span class="c1">// Match [ref]: target
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;(?m)^\s*\[([^\]]+)\]:\s*(.+)$&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">REFERENCE_USE_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">    </span><span class="c1">// Match [text][ref]
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;\[([^\]]+)\]\[([^\]]+)\]&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and extract all links
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="sd"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">parse_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">MarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">links</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">    </span><span class="c1">// First, collect reference definitions
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">reference_defs</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashMap</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">REFERENCE_DEF_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">trim</span><span class="p">().</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">        </span><span class="n">reference_defs</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">ref_name</span><span class="p">,</span><span class="w"> </span><span class="n">ref_target</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">    </span><span class="c1">// Track code block state
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">in_code_block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">    </span><span class="c1">// Parse line by line
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">line_num</span><span class="p">,</span><span class="w"> </span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">content</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">line_number</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">line_num</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="c1">// 1-indexed
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">        </span><span class="c1">// Check for code block markers
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">line</span><span class="p">.</span><span class="n">trim_start</span><span class="p">().</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;```&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="w">            </span><span class="n">in_code_block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="n">in_code_block</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w">        </span><span class="c1">// Skip content inside code blocks
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">in_code_block</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="w">        </span><span class="c1">// Parse inline links [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target)
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">INLINE_LINK_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="w">            </span><span class="n">links</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="w">                </span><span class="n">text</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="w">                </span><span class="n">target</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="w">                </span><span class="n">line</span>: <span class="nc">line_number</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="w">            </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="w">        </span><span class="c1">// Parse reference links [text][ref]
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">REFERENCE_USE_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">to_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">target</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">reference_defs</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ref_name</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="w">                </span><span class="n">links</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="w">                    </span><span class="n">text</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="w">                    </span><span class="n">target</span>: <span class="nc">target</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="w">                    </span><span class="n">line</span>: <span class="nc">line_number</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="w">                </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="w">    </span><span class="n">links</span><span class="w">
</span></span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h4 id="步驟-3用-pyo3-導出-python-介面">步驟 3：用 PyO3 導出 Python 介面</h4>
<p>將 Rust 結構與函式導出給 Python 使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">types</span>::<span class="n">PyDict</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="w"></span><span class="sd">/// Python-visible link structure
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">text</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">target</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">line</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="w">            </span><span class="s">&#34;MarkdownLink(text=&#39;</span><span class="si">{}</span><span class="s">&#39;, target=&#39;</span><span class="si">{}</span><span class="s">&#39;, line=</span><span class="si">{}</span><span class="s">)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">text</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">target</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">line</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="w">        </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="w">    </span><span class="sd">/// Convert to Python dict for compatibility
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">to_dict</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">py</span>: <span class="nc">Python</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Bound</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="p">,</span><span class="w"> </span><span class="n">PyDict</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">dict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PyDict</span>::<span class="n">new</span><span class="p">(</span><span class="n">py</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;text&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="n">text</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;target&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="n">target</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;line&#34;</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">line</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="nb">From</span><span class="o">&lt;</span><span class="n">MarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">link</span>: <span class="nc">MarkdownLink</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="w">        </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="w">            </span><span class="n">text</span>: <span class="nc">link</span><span class="p">.</span><span class="n">text</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="w">            </span><span class="n">target</span>: <span class="nc">link</span><span class="p">.</span><span class="n">target</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="w">            </span><span class="n">line</span>: <span class="nc">link</span><span class="p">.</span><span class="n">line</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and return list of links as objects
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">PyMarkdownLink</span>::<span class="n">from</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and return list of links as dicts
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="sd">/// (for drop-in compatibility with existing Python code)
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_markdown_links_as_dicts</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="w">    </span><span class="n">py</span>: <span class="nc">Python</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="w">    </span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Bound</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="p">,</span><span class="w"> </span><span class="n">PyDict</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">link</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">dict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PyDict</span>::<span class="n">new</span><span class="p">(</span><span class="n">py</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;text&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">text</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;target&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;line&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">line</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="w"></span><span class="sd">/// Filter out external links, keeping only internal links
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">filter_internal_links</span><span class="p">(</span><span class="n">links</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="w">    </span><span class="n">links</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="n">link</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="w">            </span><span class="c1">// Skip pure anchor links
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="sc">&#39;#&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="w">            </span><span class="c1">// Skip external links
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;http://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="w">                </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;https://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="w">                </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;mailto:&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="w">                </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;tel:&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="w">                </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;ftp://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="w">            </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="w"></span><span class="sd">/// Python module definition
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="sd"></span><span class="cp">#[pymodule]</span><span class="w">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">markdown_parser_rs</span><span class="p">(</span><span class="n">m</span>: <span class="kp">&amp;</span><span class="nc">Bound</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="p">,</span><span class="w"> </span><span class="n">PyModule</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">parse_markdown_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">parse_markdown_links_as_dicts</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">filter_internal_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h4 id="步驟-4建置與測試">步驟 4：建置與測試</h4>





<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"><span class="c1"># 開發模式建置（快速，用於測試）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">maturin develop
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 或者以 release 模式建置（優化效能）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">maturin develop --release
</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"># 建置 wheel 套件</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">maturin build --release
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 安裝到當前環境</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">pip install target/wheels/markdown_parser_rs-*.whl</span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<p>以下是完整的 <code>src/lib.rs</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">  1</span><span class="cl"><span class="sd">//! Markdown Link Parser - A high-performance parser written in Rust
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="sd">//!
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="sd">//! This module provides fast markdown link parsing capabilities
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="sd">//! using Rust&#39;s regex crate and PyO3 for Python bindings.
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="sd"></span><span class="w">
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">types</span>::<span class="n">PyDict</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="n">Regex</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1">// Core Data Structures
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="w"></span><span class="sd">/// Internal link representation
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="sd"></span><span class="cp">#[derive(Debug, Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="w">    </span><span class="n">text</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="w">    </span><span class="n">target</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="w">    </span><span class="n">line</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="w"></span><span class="sd">/// Python-visible link structure with getter methods
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">text</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">target</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">line</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="w">    </span><span class="sd">/// String representation for debugging
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="w">            </span><span class="s">&#34;MarkdownLink(text=&#39;</span><span class="si">{}</span><span class="s">&#39;, target=&#39;</span><span class="si">{}</span><span class="s">&#39;, line=</span><span class="si">{}</span><span class="s">)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">text</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">target</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">line</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="w">        </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="w">    </span><span class="sd">/// Convert to Python dict for compatibility with existing code
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">to_dict</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">py</span>: <span class="nc">Python</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Bound</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="p">,</span><span class="w"> </span><span class="n">PyDict</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">dict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PyDict</span>::<span class="n">new</span><span class="p">(</span><span class="n">py</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;text&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="n">text</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;target&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="bp">self</span><span class="p">.</span><span class="n">target</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;line&#34;</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">line</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="w">        </span><span class="n">dict</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="nb">From</span><span class="o">&lt;</span><span class="n">MarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">link</span>: <span class="nc">MarkdownLink</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="w">        </span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="w">            </span><span class="n">text</span>: <span class="nc">link</span><span class="p">.</span><span class="n">text</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="w">            </span><span class="n">target</span>: <span class="nc">link</span><span class="p">.</span><span class="n">target</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="w">            </span><span class="n">line</span>: <span class="nc">link</span><span class="p">.</span><span class="n">line</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="c1">// Pre-compiled Regex Patterns
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="w"></span><span class="c1">// Inline link: [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/target), excluding images ![alt](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/src)
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">INLINE_LINK_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="w"></span><span class="c1">// Reference definition: [ref]: target
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">REFERENCE_DEF_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;(?m)^\s*\[([^\]]+)\]:\s*(.+)$&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="w"></span><span class="c1">// Reference usage: [text][ref]
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">REFERENCE_USE_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;\[([^\]]+)\]\[([^\]]+)\]&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="c1">// Core Parsing Logic
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and extract all links
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="sd">/// This function handles:
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="sd">/// - Inline links: [text](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/url)
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="sd">/// - Reference links: [text][ref] with [ref]: url definitions
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="sd">/// - Code block detection (skips links inside ```)
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="sd"></span><span class="k">fn</span> <span class="nf">parse_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">MarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">links</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="w">    </span><span class="c1">// Phase 1: Collect all reference definitions
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">reference_defs</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashMap</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">REFERENCE_DEF_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">trim</span><span class="p">().</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="w">        </span><span class="n">reference_defs</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">ref_name</span><span class="p">,</span><span class="w"> </span><span class="n">ref_target</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="w">    </span><span class="c1">// Phase 2: Parse links line by line
</span></span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">in_code_block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">line_num</span><span class="p">,</span><span class="w"> </span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">content</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">line_number</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">line_num</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="c1">// Convert to 1-indexed
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="w">        </span><span class="c1">// Toggle code block state
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">line</span><span class="p">.</span><span class="n">trim_start</span><span class="p">().</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;```&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="w">            </span><span class="n">in_code_block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="n">in_code_block</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="w">        </span><span class="c1">// Skip content inside code blocks
</span></span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">in_code_block</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="w">        </span><span class="c1">// Extract inline links
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">INLINE_LINK_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="w">            </span><span class="n">links</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="w">                </span><span class="n">text</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="w">                </span><span class="n">target</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="w">                </span><span class="n">line</span>: <span class="nc">line_number</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="w">            </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="w">        </span><span class="c1">// Extract reference-style links
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">REFERENCE_USE_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">ref_name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">to_lowercase</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">target</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">reference_defs</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ref_name</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="w">                </span><span class="n">links</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">MarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="w">                    </span><span class="n">text</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="w">                    </span><span class="n">target</span>: <span class="nc">target</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="w">                    </span><span class="n">line</span>: <span class="nc">line_number</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="w">                </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="w">    </span><span class="n">links</span><span class="w">
</span></span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="w"></span><span class="sd">/// Check if a link target is external
</span></span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="sd"></span><span class="k">fn</span> <span class="nf">is_external_link</span><span class="p">(</span><span class="n">target</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="w">    </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;http://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="w">        </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;https://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="w">        </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;mailto:&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="w">        </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;tel:&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="w">        </span><span class="o">||</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;ftp://&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="c1">// Python Interface Functions
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and return a list of MarkdownLink objects
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="sd">/// Args:
</span></span></span><span class="line"><span class="ln">164</span><span class="cl"><span class="sd">///     content: The markdown content to parse
</span></span></span><span class="line"><span class="ln">165</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="sd">/// Returns:
</span></span></span><span class="line"><span class="ln">167</span><span class="cl"><span class="sd">///     List of MarkdownLink objects
</span></span></span><span class="line"><span class="ln">168</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">169</span><span class="cl"><span class="sd">/// Example:
</span></span></span><span class="line"><span class="ln">170</span><span class="cl"><span class="sd">///     &gt;&gt;&gt; links = parse_markdown_links(&#34;Check [docs](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/README.md)&#34;)
</span></span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="sd">///     &gt;&gt;&gt; links[0].text
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="sd">///     &#39;docs&#39;
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="sd">///     &gt;&gt;&gt; links[0].target
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="sd">///     &#39;./README.md&#39;
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">PyMarkdownLink</span>::<span class="n">from</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="w"></span><span class="sd">/// Parse markdown content and return a list of dicts
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="sd">/// This function provides drop-in compatibility with the original
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="sd">/// Python implementation that returns dicts.
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="sd">/// Args:
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="sd">///     content: The markdown content to parse
</span></span></span><span class="line"><span class="ln">190</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="sd">/// Returns:
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="sd">///     List of dicts with keys: text, target, line
</span></span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_markdown_links_as_dicts</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="w">    </span><span class="n">py</span>: <span class="nc">Python</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">196</span><span class="cl"><span class="w">    </span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Bound</span><span class="o">&lt;</span><span class="na">&#39;py</span><span class="p">,</span><span class="w"> </span><span class="n">PyDict</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">198</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">199</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">200</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">link</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">201</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">dict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PyDict</span>::<span class="n">new</span><span class="p">(</span><span class="n">py</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">202</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;text&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">text</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">203</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;target&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">204</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="p">.</span><span class="n">set_item</span><span class="p">(</span><span class="s">&#34;line&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">link</span><span class="p">.</span><span class="n">line</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">205</span><span class="cl"><span class="w">            </span><span class="n">dict</span><span class="w">
</span></span></span><span class="line"><span class="ln">206</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">208</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">210</span><span class="cl"><span class="w"></span><span class="sd">/// Filter links to keep only internal ones
</span></span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="sd">/// Removes:
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="sd">/// - External links (http://, https://, mailto:, etc.)
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="sd">/// - Pure anchor links (#section)
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="sd">/// Args:
</span></span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="sd">///     links: List of MarkdownLink objects
</span></span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">219</span><span class="cl"><span class="sd">/// Returns:
</span></span></span><span class="line"><span class="ln">220</span><span class="cl"><span class="sd">///     Filtered list of internal links
</span></span></span><span class="line"><span class="ln">221</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">222</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">filter_internal_links</span><span class="p">(</span><span class="n">links</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">223</span><span class="cl"><span class="w">    </span><span class="n">links</span><span class="w">
</span></span></span><span class="line"><span class="ln">224</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">225</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="n">link</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">226</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">227</span><span class="cl"><span class="w">            </span><span class="c1">// Skip pure anchor links
</span></span></span><span class="line"><span class="ln">228</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="sc">&#39;#&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">229</span><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="w">            </span><span class="c1">// Skip external links
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="o">!</span><span class="n">is_external_link</span><span class="p">(</span><span class="n">target</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">236</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">237</span><span class="cl"><span class="w"></span><span class="sd">/// Count total links in content (fast path, no object creation)
</span></span></span><span class="line"><span class="ln">238</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">239</span><span class="cl"><span class="sd">/// Args:
</span></span></span><span class="line"><span class="ln">240</span><span class="cl"><span class="sd">///     content: The markdown content to parse
</span></span></span><span class="line"><span class="ln">241</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">242</span><span class="cl"><span class="sd">/// Returns:
</span></span></span><span class="line"><span class="ln">243</span><span class="cl"><span class="sd">///     Number of links found
</span></span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">count_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">usize</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">).</span><span class="n">len</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">247</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">248</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">249</span><span class="cl"><span class="w"></span><span class="sd">/// Parse and filter in one pass (most efficient for link checking)
</span></span></span><span class="line"><span class="ln">250</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">251</span><span class="cl"><span class="sd">/// Args:
</span></span></span><span class="line"><span class="ln">252</span><span class="cl"><span class="sd">///     content: The markdown content to parse
</span></span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">254</span><span class="cl"><span class="sd">/// Returns:
</span></span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="sd">///     List of internal MarkdownLink objects
</span></span></span><span class="line"><span class="ln">256</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">257</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_internal_links</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">258</span><span class="cl"><span class="w">    </span><span class="n">parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">259</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">into_iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">260</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="n">link</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">261</span><span class="cl"><span class="w">            </span><span class="o">!</span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">.</span><span class="n">starts_with</span><span class="p">(</span><span class="sc">&#39;#&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="n">is_external_link</span><span class="p">(</span><span class="o">&amp;</span><span class="n">link</span><span class="p">.</span><span class="n">target</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">262</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">263</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">PyMarkdownLink</span>::<span class="n">from</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">264</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">265</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">266</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">267</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">268</span><span class="cl"><span class="c1">// Module Definition
</span></span></span><span class="line"><span class="ln">269</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">270</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">271</span><span class="cl"><span class="w"></span><span class="sd">/// High-performance Markdown link parser
</span></span></span><span class="line"><span class="ln">272</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">273</span><span class="cl"><span class="sd">/// This module provides Rust-powered functions for parsing
</span></span></span><span class="line"><span class="ln">274</span><span class="cl"><span class="sd">/// and filtering markdown links.
</span></span></span><span class="line"><span class="ln">275</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">276</span><span class="cl"><span class="sd">/// Functions:
</span></span></span><span class="line"><span class="ln">277</span><span class="cl"><span class="sd">///     parse_markdown_links: Parse content, return MarkdownLink objects
</span></span></span><span class="line"><span class="ln">278</span><span class="cl"><span class="sd">///     parse_markdown_links_as_dicts: Parse content, return dicts
</span></span></span><span class="line"><span class="ln">279</span><span class="cl"><span class="sd">///     parse_internal_links: Parse and filter to internal links only
</span></span></span><span class="line"><span class="ln">280</span><span class="cl"><span class="sd">///     filter_internal_links: Filter existing links
</span></span></span><span class="line"><span class="ln">281</span><span class="cl"><span class="sd">///     count_links: Fast link counting
</span></span></span><span class="line"><span class="ln">282</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">283</span><span class="cl"><span class="sd">/// Classes:
</span></span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="sd">///     MarkdownLink: Represents a parsed link
</span></span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="sd"></span><span class="cp">#[pymodule]</span><span class="w">
</span></span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">markdown_parser_rs</span><span class="p">(</span><span class="n">m</span>: <span class="kp">&amp;</span><span class="nc">Bound</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="p">,</span><span class="w"> </span><span class="n">PyModule</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">287</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">parse_markdown_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">parse_markdown_links_as_dicts</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">290</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">filter_internal_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">291</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">count_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">parse_internal_links</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">293</span><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="ln">294</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h3 id="python-整合範例">Python 整合範例</h3>
<p>以下展示如何在現有程式碼中整合 Rust 模組：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">使用 Rust 加速的 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">這個範例展示如何用 Rust 模組替換原有的 Python 解析邏輯，
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">同時保持 API 相容性。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1"># Try to import Rust module, fallback to pure Python</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="kn">import</span> <span class="nn">markdown_parser_rs</span> <span class="k">as</span> <span class="nn">parser_rs</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">USE_RUST</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Using Rust-powered parser&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">USE_RUST</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Rust module not available, using pure Python&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Markdown link checker with optional Rust acceleration&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">use_rust</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        Initialize the checker
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">            use_rust: Whether to use Rust module if available
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">use_rust</span> <span class="o">=</span> <span class="n">use_rust</span> <span class="ow">and</span> <span class="n">USE_RUST</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        Parse markdown content and extract all links
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">            content: Markdown content
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">            List of dicts with keys: text, target, line
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">use_rust</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">            <span class="c1"># Use Rust implementation</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="k">return</span> <span class="n">parser_rs</span><span class="o">.</span><span class="n">parse_markdown_links_as_dicts</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="c1"># Fallback to pure Python (original implementation)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_python</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_internal_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">        Parse and filter to internal links only
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">            content: Markdown content
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">            List of internal link dicts
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">use_rust</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">            <span class="c1"># Use optimized Rust function that parses and filters in one pass</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">links</span> <span class="o">=</span> <span class="n">parser_rs</span><span class="o">.</span><span class="n">parse_internal_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="k">return</span> <span class="p">[</span><span class="n">link</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">all_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_python</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal</span><span class="p">(</span><span class="n">all_links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="k">def</span> <span class="nf">_parse_python</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Pure Python implementation (fallback)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="n">INLINE_LINK</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">REFERENCE_DEF</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?m)^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="n">REFERENCE_USE</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="c1"># Collect reference definitions</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">REFERENCE_DEF</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="c1"># Parse line by line</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">),</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">INLINE_LINK</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">REFERENCE_USE</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="k">def</span> <span class="nf">_filter_internal</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Filter to internal links only&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="n">external_prefixes</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">            <span class="s1">&#39;http://&#39;</span><span class="p">,</span> <span class="s1">&#39;https://&#39;</span><span class="p">,</span> <span class="s1">&#39;mailto:&#39;</span><span class="p">,</span> <span class="s1">&#39;tel:&#39;</span><span class="p">,</span> <span class="s1">&#39;ftp://&#39;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="n">link</span> <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">link</span><span class="p">[</span><span class="s1">&#39;target&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;#&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="ow">and</span> <span class="ow">not</span> <span class="n">link</span><span class="p">[</span><span class="s1">&#39;target&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">external_prefixes</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="c1"># Convenience functions</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">use_rust</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">    Check a single markdown file for broken links
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">        file_path: Path to the markdown file
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        use_rust: Whether to use Rust acceleration
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">        Dict with file_path, total_links, and internal_links count
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">(</span><span class="n">use_rust</span><span class="o">=</span><span class="n">use_rust</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="n">all_links</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="n">internal_links</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">parse_internal_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="s2">&#34;file_path&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="s2">&#34;total_links&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_links</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="s2">&#34;internal_links&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">internal_links</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="s2">&#34;links&#34;</span><span class="p">:</span> <span class="n">internal_links</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="c1"># Example usage</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="n">sample</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="s2">&#34;# Sample Document</span><span class="se">\n\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="s2">&#34;Check the [documentation](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/docs/README.md) for more info.</span><span class="se">\n\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="s2">&#34;External link: [Google](https://google.com)</span><span class="se">\n\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="s2">&#34;Reference style: [API docs][api]</span><span class="se">\n\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="s2">&#34;[api]: ./api/reference.md</span><span class="se">\n\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">        <span class="s2">&#34;~~~python</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="s2">&#34;# This [link](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/should_be_ignored.md) is in a code block</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="s2">&#34;~~~</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">sample</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">
</span></span><span class="line"><span class="ln">165</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;All links found:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Line </span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;line&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/</span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;target&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Internal links only:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="n">internal</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">parse_internal_links</span><span class="p">(</span><span class="n">sample</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Line </span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;line&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/</span><span class="si">{</span><span class="n">link</span><span class="p">[</span><span class="s1">&#39;target&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能比較">效能比較</h3>
<p>以下是完整的效能測試腳本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Performance comparison: Python vs Cython vs Rust
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">This script benchmarks the three implementations on
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">various markdown file sizes.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">statistics</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1"># Generate test data</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">def</span> <span class="nf">generate_markdown</span><span class="p">(</span><span class="n">num_links</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate markdown content with specified number of links&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;# Test Document</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">            <span class="c1"># Inline link</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check [link</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/path/to/file</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.md) for info.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="c1"># External link (should be filtered)</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Visit [site</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](https://example</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.com)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="c1"># Reference style link</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;See [doc</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">][ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]: ./docs/page</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.md</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">            <span class="c1"># Anchor link (should be filtered)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Jump to [section</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](#section-</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="c1"># Regular text</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;This is paragraph </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2"> with some text.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="c1"># Add occasional code blocks (using ~~~ to avoid markdown parsing issues)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">20</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;~~~python</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;# [fake link](/python-advanced/06-rust-extensions/case-studies/pyo3-parser/should_ignore_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.md)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;print(&#39;hello&#39;)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;~~~</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">str</span><span class="p">],</span> <span class="n">List</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">    Benchmark a function
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">        Tuple of (mean_time_ms, min_time_ms, max_time_ms)
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="c1"># Warmup</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">func</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="c1"># Actual benchmark</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="n">func</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span>  <span class="c1"># Convert to ms</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="n">statistics</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="nb">min</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">def</span> <span class="nf">run_benchmarks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Run benchmarks comparing all implementations&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="c1"># Import implementations</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="c1"># Pure Python implementation</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">INLINE_LINK</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_python</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">),</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">INLINE_LINK</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="c1"># Try to import Rust implementation</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="kn">import</span> <span class="nn">markdown_parser_rs</span> <span class="k">as</span> <span class="nn">rust_parser</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="n">has_rust</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">has_rust</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Rust module not available&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">    <span class="c1"># Test sizes</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="n">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">100</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">5000</span><span class="p">,</span> <span class="mi">10000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Markdown Link Parser Benchmark&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">sizes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">generate_markdown</span><span class="p">(</span><span class="n">size</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">content_kb</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">))</span> <span class="o">/</span> <span class="mi">1024</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Test: </span><span class="si">{</span><span class="n">size</span><span class="si">}</span><span class="s2"> links (~</span><span class="si">{</span><span class="n">content_kb</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="c1"># Python benchmark</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="n">py_mean</span><span class="p">,</span> <span class="n">py_min</span><span class="p">,</span> <span class="n">py_max</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">parse_python</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Python:  </span><span class="si">{</span><span class="n">py_mean</span><span class="si">:</span><span class="s2">8.3f</span><span class="si">}</span><span class="s2"> ms (min: </span><span class="si">{</span><span class="n">py_min</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">, max: </span><span class="si">{</span><span class="n">py_max</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="c1"># Rust benchmark</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="k">if</span> <span class="n">has_rust</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">            <span class="n">rs_mean</span><span class="p">,</span> <span class="n">rs_min</span><span class="p">,</span> <span class="n">rs_max</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">                <span class="n">rust_parser</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">                <span class="n">content</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="n">speedup</span> <span class="o">=</span> <span class="n">py_mean</span> <span class="o">/</span> <span class="n">rs_mean</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Rust:    </span><span class="si">{</span><span class="n">rs_mean</span><span class="si">:</span><span class="s2">8.3f</span><span class="si">}</span><span class="s2"> ms (min: </span><span class="si">{</span><span class="n">rs_min</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">, max: </span><span class="si">{</span><span class="n">rs_max</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Speedup: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x faster&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="n">size</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">            <span class="s2">&#34;python_ms&#34;</span><span class="p">:</span> <span class="n">py_mean</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="s2">&#34;rust_ms&#34;</span><span class="p">:</span> <span class="n">rs_mean</span> <span class="k">if</span> <span class="n">has_rust</span> <span class="k">else</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">            <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">speedup</span> <span class="k">if</span> <span class="n">has_rust</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl">    <span class="c1"># Summary table</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Summary&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Links&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Python (ms)&#39;</span><span class="si">:</span><span class="s2">&lt;15</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Rust (ms)&#39;</span><span class="si">:</span><span class="s2">&lt;15</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Speedup&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">rust_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">&#39;rust_ms&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">if</span> <span class="n">r</span><span class="p">[</span><span class="s1">&#39;rust_ms&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;N/A&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">speedup_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span> <span class="k">if</span> <span class="n">r</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;N/A&#34;</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">&#39;size&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">r</span><span class="p">[</span><span class="s1">&#39;python_ms&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;15.3f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">rust_str</span><span class="si">:</span><span class="s2">&lt;15</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">speedup_str</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">
</span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="n">run_benchmarks</span><span class="p">()</span></span></span></code></pre></div><p><strong>典型效能結果</strong>：</p>
<table>
  <thead>
      <tr>
          <th>連結數</th>
          <th>Python (ms)</th>
          <th>Rust (ms)</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>100</td>
          <td>0.45</td>
          <td>0.03</td>
          <td>15x</td>
      </tr>
      <tr>
          <td>500</td>
          <td>2.10</td>
          <td>0.12</td>
          <td>18x</td>
      </tr>
      <tr>
          <td>1000</td>
          <td>4.25</td>
          <td>0.22</td>
          <td>19x</td>
      </tr>
      <tr>
          <td>5000</td>
          <td>21.50</td>
          <td>1.05</td>
          <td>20x</td>
      </tr>
      <tr>
          <td>10000</td>
          <td>43.80</td>
          <td>2.10</td>
          <td>21x</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>注意：實際效能取決於硬體和內容複雜度。Rust 的優勢在大型檔案上更加明顯。</p></blockquote>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Python</th>
          <th>Cython</th>
          <th>Rust (PyO3)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發速度</td>
          <td>快（數小時）</td>
          <td>中（數天）</td>
          <td>慢（數天至週）</td>
      </tr>
      <tr>
          <td>執行速度</td>
          <td>1x</td>
          <td>2-10x</td>
          <td>10-100x</td>
      </tr>
      <tr>
          <td>記憶體安全</td>
          <td>GC 管理</td>
          <td>GC 管理</td>
          <td>編譯時保證</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>低</td>
          <td>中</td>
          <td>高</td>
      </tr>
      <tr>
          <td>除錯難度</td>
          <td>低</td>
          <td>中</td>
          <td>高</td>
      </tr>
      <tr>
          <td>部署複雜度</td>
          <td>低</td>
          <td>中</td>
          <td>中</td>
      </tr>
      <tr>
          <td>跨平台支援</td>
          <td>優秀</td>
          <td>需編譯</td>
          <td>需編譯</td>
      </tr>
      <tr>
          <td>生態系統</td>
          <td>豐富</td>
          <td>有限</td>
          <td>豐富（Cargo）</td>
      </tr>
  </tbody>
</table>
<h3 id="選擇決策樹">選擇決策樹</h3>





<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">需要加速 Python 程式碼？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 否 → 保持純 Python
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── 是 → 效能需求多高？
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ├── 2-5x 足夠 → 考慮 Cython
</span></span><span class="line"><span class="ln">5</span><span class="cl">    └── 需要 10x+ → 團隊有 Rust 經驗？
</span></span><span class="line"><span class="ln">6</span><span class="cl">        ├── 是 → 使用 PyO3
</span></span><span class="line"><span class="ln">7</span><span class="cl">        └── 否 → 效能瓶頸明確嗎？
</span></span><span class="line"><span class="ln">8</span><span class="cl">            ├── 是 → 值得學習 Rust
</span></span><span class="line"><span class="ln">9</span><span class="cl">            └── 否 → 先用 Cython，後續再評估</span></span></code></pre></div><h2 id="什麼時候該用-rust">什麼時候該用 Rust？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要極致效能（10x+ 加速）</li>
<li>CPU 密集的核心邏輯</li>
<li>需要處理大量資料</li>
<li>團隊有 Rust 經驗</li>
<li>需要記憶體安全保證</li>
<li>可利用 Rust 生態系統（如 regex, rayon）</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>效能需求不高</li>
<li>快速原型開發</li>
<li>團隊不熟悉 Rust</li>
<li>專案生命週期短</li>
<li>I/O 密集型任務（瓶頸不在 CPU）</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="練習-1基礎練習---字串處理函式">練習 1：基礎練習 - 字串處理函式</h3>
<p>用 PyO3 實作一個字串處理函式，將 Markdown 標題轉換為 slug：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 目標：將 &#34;Hello World! 你好&#34; 轉換為 &#34;hello-world-你好&#34;
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">slugify</span><span class="p">(</span><span class="n">title</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="c1">// 你的實作
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="fm">todo!</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><p><strong>提示</strong>：</p>
<ul>
<li>轉換為小寫</li>
<li>移除特殊字元</li>
<li>用連字號替換空白</li>
</ul>
<p><strong>參考解答</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">slugify</span><span class="p">(</span><span class="n">title</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">title</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">chars</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">is_alphanumeric</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">                </span><span class="n">c</span><span class="p">.</span><span class="n">to_lowercase</span><span class="p">().</span><span class="n">next</span><span class="p">().</span><span class="n">unwrap_or</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">is_whitespace</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">                </span><span class="sc">&#39;-&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">                </span><span class="c1">// Keep non-ASCII chars (like CJK)
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">is_ascii</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="sc">&#39;\0&#39;</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|&amp;</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="sc">&#39;\0&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span>::<span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="c1">// Clean up multiple consecutive dashes
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="sc">&#39;-&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="n">s</span><span class="o">|</span><span class="w"> </span><span class="o">!</span><span class="n">s</span><span class="p">.</span><span class="n">is_empty</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span>::<span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">_</span><span class="o">&gt;&gt;</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="s">&#34;-&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h3 id="練習-2進階練習---模式匹配">練習 2：進階練習 - 模式匹配</h3>
<p>用 regex crate 實作一個函式，提取 Markdown 文件中的所有標題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 目標：提取 # 標題，## 標題，### 標題 等
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">Heading</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="n">level</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="n">text</span>: <span class="nb">String</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="n">line</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">extract_headings</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Heading</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="c1">// 你的實作
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="fm">todo!</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><p><strong>提示</strong>：</p>
<ul>
<li>使用 <code>^#{1,6}\s+(.+)$</code> 正則表達式</li>
<li>記得處理 multiline 模式</li>
</ul>
<p><strong>參考解答</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="n">Regex</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">HEADING_PATTERN</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;(?m)^(#{1,6})\s+(.+)$&#34;</span><span class="p">).</span><span class="n">unwrap</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">extract_headings</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">Heading</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">headings</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">current_line</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">last_end</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">HEADING_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">content</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">match_start</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="n">unwrap</span><span class="p">().</span><span class="n">start</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="c1">// Count newlines to determine line number
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="n">current_line</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">content</span><span class="p">[</span><span class="n">last_end</span><span class="o">..</span><span class="n">match_start</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">chars</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|&amp;</span><span class="n">c</span><span class="o">|</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">&#39;\n&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">count</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">        </span><span class="n">last_end</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">match_start</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">level</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">trim</span><span class="p">().</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">        </span><span class="n">headings</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Heading</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">            </span><span class="n">level</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">            </span><span class="n">text</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">            </span><span class="n">line</span>: <span class="nc">current_line</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">    </span><span class="n">headings</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h3 id="練習-3挑戰題---串流解析">練習 3：挑戰題 - 串流解析</h3>
<p>實作一個可處理大型檔案的串流解析器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="p">{</span><span class="n">BufRead</span><span class="p">,</span><span class="w"> </span><span class="n">BufReader</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">fs</span>::<span class="n">File</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">StreamingParser</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="c1">// 你的實作
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">StreamingParser</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="cp">#[new]</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">file_path</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">        </span><span class="c1">// 開啟檔案
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="fm">todo!</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="sd">/// 迭代器協議
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">__iter__</span><span class="p">(</span><span class="n">slf</span>: <span class="nc">PyRef</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyRef</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="n">slf</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__next__</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">        </span><span class="c1">// 讀取下一個連結
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="fm">todo!</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><p><strong>提示</strong>：</p>
<ul>
<li>使用 <code>BufReader</code> 逐行讀取</li>
<li>維護狀態（行號、程式碼區塊）</li>
<li>實作 Python 迭代器協議</li>
</ul>
<p><strong>參考解答思路</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">fs</span>::<span class="n">File</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="p">{</span><span class="n">BufRead</span><span class="p">,</span><span class="w"> </span><span class="n">BufReader</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="k">struct</span> <span class="nc">StreamingParser</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="n">reader</span>: <span class="nc">BufReader</span><span class="o">&lt;</span><span class="n">File</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="n">line_number</span>: <span class="kt">usize</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="n">in_code_block</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="c1">// Buffer for pending links found on current line
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">pending_links</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">StreamingParser</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="cp">#[new]</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">file_path</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">File</span>::<span class="n">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="o">|</span><span class="n">e</span><span class="o">|</span><span class="w"> </span><span class="n">PyErr</span>::<span class="n">new</span>::<span class="o">&lt;</span><span class="n">pyo3</span>::<span class="n">exceptions</span>::<span class="n">PyIOError</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="o">&gt;</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">                </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;Cannot open file: </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">e</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">            </span><span class="p">))</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">        </span><span class="nb">Ok</span><span class="p">(</span><span class="n">StreamingParser</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">            </span><span class="n">reader</span>: <span class="nc">BufReader</span>::<span class="n">new</span><span class="p">(</span><span class="n">file</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">            </span><span class="n">line_number</span>: <span class="mi">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">            </span><span class="n">in_code_block</span>: <span class="nc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">            </span><span class="n">pending_links</span>: <span class="nb">Vec</span>::<span class="n">new</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__iter__</span><span class="p">(</span><span class="n">slf</span>: <span class="nc">PyRef</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyRef</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">        </span><span class="n">slf</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__next__</span><span class="p">(</span><span class="k">mut</span><span class="w"> </span><span class="n">slf</span>: <span class="nc">PyRefMut</span><span class="o">&lt;</span><span class="bp">Self</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">        </span><span class="c1">// Return pending links first
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">link</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">slf</span><span class="p">.</span><span class="n">pending_links</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">link</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">        </span><span class="c1">// Read and parse lines until we find links
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">line</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">String</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">        </span><span class="k">loop</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">            </span><span class="n">line</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">            </span><span class="k">match</span><span class="w"> </span><span class="n">slf</span><span class="p">.</span><span class="n">reader</span><span class="p">.</span><span class="n">read_line</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">                </span><span class="nb">Ok</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">None</span><span class="p">,</span><span class="w"> </span><span class="c1">// EOF
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"></span><span class="w">                </span><span class="nb">Ok</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w">                    </span><span class="n">slf</span><span class="p">.</span><span class="n">line_number</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">                    </span><span class="c1">// Handle code blocks
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="c1"></span><span class="w">                    </span><span class="k">if</span><span class="w"> </span><span class="n">line</span><span class="p">.</span><span class="n">trim_start</span><span class="p">().</span><span class="n">starts_with</span><span class="p">(</span><span class="s">&#34;```&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="w">                        </span><span class="n">slf</span><span class="p">.</span><span class="n">in_code_block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="n">slf</span><span class="p">.</span><span class="n">in_code_block</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w">                        </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="w">                    </span><span class="k">if</span><span class="w"> </span><span class="n">slf</span><span class="p">.</span><span class="n">in_code_block</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="w">                        </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="w">                    </span><span class="c1">// Parse links from this line
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="c1"></span><span class="w">                    </span><span class="kd">let</span><span class="w"> </span><span class="n">links</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">parse_line_links</span><span class="p">(</span><span class="o">&amp;</span><span class="n">line</span><span class="p">,</span><span class="w"> </span><span class="n">slf</span><span class="p">.</span><span class="n">line_number</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="w">                    </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">links</span><span class="p">.</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="w">                        </span><span class="n">slf</span><span class="p">.</span><span class="n">pending_links</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">links</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="w">                        </span><span class="k">return</span><span class="w"> </span><span class="n">slf</span><span class="p">.</span><span class="n">pending_links</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="w">                </span><span class="nb">Err</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">None</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">parse_line_links</span><span class="p">(</span><span class="n">line</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w"> </span><span class="n">line_number</span>: <span class="kt">usize</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">PyMarkdownLink</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">links</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">cap</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="no">INLINE_LINK_PATTERN</span><span class="p">.</span><span class="n">captures_iter</span><span class="p">(</span><span class="n">line</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="w">        </span><span class="n">links</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">PyMarkdownLink</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="w">            </span><span class="n">text</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="w">            </span><span class="n">target</span>: <span class="nc">cap</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="w">            </span><span class="n">line</span>: <span class="nc">line_number</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="w">    </span><span class="n">links</span><span class="w">
</span></span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://pyo3.rs/">PyO3 官方文件</a>：完整的 PyO3 指南</li>
<li><a href="https://www.maturin.rs/">Maturin 官方文件</a>：Rust Python 套件建置工具</li>
<li><a href="https://docs.rs/regex/">Rust regex crate</a>：高效能正則表達式</li>
<li><a href="https://pyo3.rs/v0.22.0/guide">PyO3 使用者指南</a>：進階用法</li>
<li><a href="https://doc.rust-lang.org/book/">Rust 程式設計語言</a>：官方 Rust 教學</li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/06-rust-extensions/case-studies/rust-regex/" data-link-title="案例：Rust 正則表達式" data-link-desc="用 Rust regex crate 加速 Hook 驗證器的模式匹配">Rust 正則表達式</a></p>
]]></content:encoded></item><item><title>案例：打包共用庫</title><link>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/package-library/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/package-library/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib&lt;/code> 整體結構，展示如何將內部共用庫打包成可重用的 Python 套件。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">模組六：打包與發布&lt;/a>&lt;/li>
&lt;li>Python 模組與套件基礎&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib&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">.claude/lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── __init__.py # Package entry point with version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── config_loader.py # YAML/JSON configuration loader
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── git_utils.py # Git operations (branch, worktree)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── hook_io.py # Hook I/O standardization
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── hook_logging.py # Logging system for hooks
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── hook_validator.py # Hook compliance validator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── markdown_link_checker.py # Markdown link validation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── README.md # API documentation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── tests/ # Unit tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> ├── test_config_loader.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> ├── test_git_utils.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> ├── test_hook_io.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> └── test_hook_logging.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是一個典型的內部工具庫，包含四個核心模組：&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>&lt;code>config_loader&lt;/code>&lt;/td>
 &lt;td>YAML/JSON 配置載入&lt;/td>
 &lt;td>PyYAML (optional)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>git_utils&lt;/code>&lt;/td>
 &lt;td>Git 命令執行與分支管理&lt;/td>
 &lt;td>無（使用 subprocess）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>hook_io&lt;/code>&lt;/td>
 &lt;td>Hook 輸入輸出標準化&lt;/td>
 &lt;td>無（使用標準庫）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>hook_logging&lt;/code>&lt;/td>
 &lt;td>日誌系統設定&lt;/td>
 &lt;td>無（使用標準庫）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="現有限制">現有限制&lt;/h3>
&lt;p>作為內部目錄的問題：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>無法跨專案重用&lt;/strong>：程式碼被鎖定在單一專案中&lt;/li>
&lt;li>&lt;strong>沒有版本管理&lt;/strong>：無法追蹤 API 變更&lt;/li>
&lt;li>&lt;strong>無法透過 pip 安裝&lt;/strong>：其他專案必須複製程式碼&lt;/li>
&lt;li>&lt;strong>相依性管理不明確&lt;/strong>：PyYAML 是可選還是必要？&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立標準的 Python 套件結構&lt;/strong>&lt;/li>
&lt;li>&lt;strong>使用 pyproject.toml 管理元資料&lt;/strong>&lt;/li>
&lt;li>&lt;strong>支援 pip install&lt;/strong>&lt;/li>
&lt;li>&lt;strong>建立 CI/CD 發布流程&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1重組目錄結構">步驟 1：重組目錄結構&lt;/h4>
&lt;p>從內部目錄結構轉換為標準的 &lt;strong>src layout&lt;/strong>：&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">claude-hooks-lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── pyproject.toml # Package metadata and build config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── README.md # Package documentation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── LICENSE # License file (MIT recommended)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── CHANGELOG.md # Version history
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ └── claude_hooks_lib/ # Package directory (underscore for import)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ ├── __init__.py # Public API exports
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ ├── config_loader.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ ├── git_utils.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ ├── hook_io.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ ├── hook_logging.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ ├── hook_validator.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">│ └── py.typed # PEP 561 marker for type hints
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> ├── conftest.py # Pytest fixtures
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> ├── test_config_loader.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> ├── test_git_utils.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> ├── test_hook_io.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> └── test_hook_logging.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="為什麼選擇-src-layout">為什麼選擇 src layout？&lt;/h5>





&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"># Flat layout (不推薦用於套件發布)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">my-package/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── my_package/ # Package 直接在根目錄
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ └── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">└── tests/
&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"># Src layout (推薦)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">my-package/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ └── my_package/ # Package 在 src/ 下
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ └── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">└── tests/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>特性&lt;/th>
 &lt;th>Flat Layout&lt;/th>
 &lt;th>Src Layout&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;/tbody>
&lt;/table>
&lt;h4 id="步驟-2建立-pyprojecttoml">步驟 2：建立 pyproject.toml&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hatchling&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatchling.build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&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 class="nx">project&lt;/span>&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;claude-hooks-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.28.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">description&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Shared utilities for Claude Code hooks&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">readme&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">license&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;MIT&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">requires-python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;=3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">authors&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">13&lt;/span>&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Your Name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;your.email@example.com&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nx">keywords&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;claude&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;utilities&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">classifiers&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">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Development Status :: 4 - Beta&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Intended Audience :: Developers&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;License :: OSI Approved :: MIT License&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Operating System :: OS Independent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.10&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.11&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.12&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.13&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Typing :: Typed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="c"># Core dependencies (minimal)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">optional-dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="c"># YAML support&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="nx">yaml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;PyYAML&amp;gt;=6.0&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="c"># Development dependencies&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="nx">dev&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">38&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&amp;gt;=8.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest-cov&amp;gt;=4.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;mypy&amp;gt;=1.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff&amp;gt;=0.4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="c"># All optional features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;claude-hooks-lib[yaml]&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">urls&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="nx">Homepage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/claude-hooks-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="nx">Documentation&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/claude-hooks-lib#readme&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">&lt;span class="nx">Repository&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/claude-hooks-lib.git&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">&lt;span class="nx">Changelog&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/claude-hooks-lib/blob/main/CHANGELOG.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">&lt;span class="c"># Command-line entry points&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">&lt;span class="nx">hook-validator&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;claude_hooks_lib.hook_validator:main&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">targets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sdist&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">&lt;span class="nx">include&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">59&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/src&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">targets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">wheel&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl">&lt;span class="nx">packages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src/claude_hooks_lib&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="關鍵設定說明">關鍵設定說明&lt;/h5>
&lt;ol>
&lt;li>&lt;strong>build-system&lt;/strong>：使用 Hatch 作為建構後端（現代、快速）&lt;/li>
&lt;li>&lt;strong>requires-python&lt;/strong>：指定最低 Python 版本&lt;/li>
&lt;li>&lt;strong>dependencies&lt;/strong>：核心相依性保持為空（僅使用標準庫）&lt;/li>
&lt;li>&lt;strong>optional-dependencies&lt;/strong>：將 PyYAML 設為可選&lt;/li>
&lt;li>&lt;strong>project.scripts&lt;/strong>：定義命令列工具入口點&lt;/li>
&lt;/ol>
&lt;h4 id="步驟-3處理相依性">步驟 3：處理相依性&lt;/h4>
&lt;p>相依性分層策略：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib</code> 整體結構，展示如何將內部共用庫打包成可重用的 Python 套件。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">模組六：打包與發布</a></li>
<li>Python 模組與套件基礎</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib</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">.claude/lib/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── __init__.py              # Package entry point with version
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── config_loader.py         # YAML/JSON configuration loader
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── git_utils.py             # Git operations (branch, worktree)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── hook_io.py               # Hook I/O standardization
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── hook_logging.py          # Logging system for hooks
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── hook_validator.py        # Hook compliance validator
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── markdown_link_checker.py # Markdown link validation
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── README.md                # API documentation
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── tests/                   # Unit tests
</span></span><span class="line"><span class="ln">11</span><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="ln">12</span><span class="cl">    ├── test_config_loader.py
</span></span><span class="line"><span class="ln">13</span><span class="cl">    ├── test_git_utils.py
</span></span><span class="line"><span class="ln">14</span><span class="cl">    ├── test_hook_io.py
</span></span><span class="line"><span class="ln">15</span><span class="cl">    └── test_hook_logging.py</span></span></code></pre></div><p>這是一個典型的內部工具庫，包含四個核心模組：</p>
<table>
  <thead>
      <tr>
          <th>模組</th>
          <th>功能</th>
          <th>相依性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>config_loader</code></td>
          <td>YAML/JSON 配置載入</td>
          <td>PyYAML (optional)</td>
      </tr>
      <tr>
          <td><code>git_utils</code></td>
          <td>Git 命令執行與分支管理</td>
          <td>無（使用 subprocess）</td>
      </tr>
      <tr>
          <td><code>hook_io</code></td>
          <td>Hook 輸入輸出標準化</td>
          <td>無（使用標準庫）</td>
      </tr>
      <tr>
          <td><code>hook_logging</code></td>
          <td>日誌系統設定</td>
          <td>無（使用標準庫）</td>
      </tr>
  </tbody>
</table>
<h3 id="現有限制">現有限制</h3>
<p>作為內部目錄的問題：</p>
<ul>
<li><strong>無法跨專案重用</strong>：程式碼被鎖定在單一專案中</li>
<li><strong>沒有版本管理</strong>：無法追蹤 API 變更</li>
<li><strong>無法透過 pip 安裝</strong>：其他專案必須複製程式碼</li>
<li><strong>相依性管理不明確</strong>：PyYAML 是可選還是必要？</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立標準的 Python 套件結構</strong></li>
<li><strong>使用 pyproject.toml 管理元資料</strong></li>
<li><strong>支援 pip install</strong></li>
<li><strong>建立 CI/CD 發布流程</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1重組目錄結構">步驟 1：重組目錄結構</h4>
<p>從內部目錄結構轉換為標準的 <strong>src layout</strong>：</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">claude-hooks-lib/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── pyproject.toml           # Package metadata and build config
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── README.md                # Package documentation
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── LICENSE                  # License file (MIT recommended)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── CHANGELOG.md             # Version history
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   └── claude_hooks_lib/    # Package directory (underscore for import)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│       ├── __init__.py      # Public API exports
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│       ├── config_loader.py
</span></span><span class="line"><span class="ln">10</span><span class="cl">│       ├── git_utils.py
</span></span><span class="line"><span class="ln">11</span><span class="cl">│       ├── hook_io.py
</span></span><span class="line"><span class="ln">12</span><span class="cl">│       ├── hook_logging.py
</span></span><span class="line"><span class="ln">13</span><span class="cl">│       ├── hook_validator.py
</span></span><span class="line"><span class="ln">14</span><span class="cl">│       └── py.typed         # PEP 561 marker for type hints
</span></span><span class="line"><span class="ln">15</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">16</span><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="ln">17</span><span class="cl">    ├── conftest.py          # Pytest fixtures
</span></span><span class="line"><span class="ln">18</span><span class="cl">    ├── test_config_loader.py
</span></span><span class="line"><span class="ln">19</span><span class="cl">    ├── test_git_utils.py
</span></span><span class="line"><span class="ln">20</span><span class="cl">    ├── test_hook_io.py
</span></span><span class="line"><span class="ln">21</span><span class="cl">    └── test_hook_logging.py</span></span></code></pre></div><h5 id="為什麼選擇-src-layout">為什麼選擇 src layout？</h5>





<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"># Flat layout (不推薦用於套件發布)
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">my-package/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── my_package/          # Package 直接在根目錄
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│   └── __init__.py
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">└── tests/
</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"># Src layout (推薦)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">my-package/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln">10</span><span class="cl">│   └── my_package/      # Package 在 src/ 下
</span></span><span class="line"><span class="ln">11</span><span class="cl">│       └── __init__.py
</span></span><span class="line"><span class="ln">12</span><span class="cl">└── tests/</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>特性</th>
          <th>Flat Layout</th>
          <th>Src Layout</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>
  </tbody>
</table>
<h4 id="步驟-2建立-pyprojecttoml">步驟 2：建立 pyproject.toml</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;claude-hooks-lib&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.28.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Shared utilities for Claude Code hooks&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">readme</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">license</span> <span class="p">=</span> <span class="s2">&#34;MIT&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">authors</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">{</span> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Your Name&#34;</span><span class="p">,</span> <span class="nx">email</span> <span class="p">=</span> <span class="s2">&#34;your.email@example.com&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">keywords</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude&#34;</span><span class="p">,</span> <span class="s2">&#34;hooks&#34;</span><span class="p">,</span> <span class="s2">&#34;utilities&#34;</span><span class="p">,</span> <span class="s2">&#34;git&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">classifiers</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;Development Status :: 4 - Beta&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;Intended Audience :: Developers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;License :: OSI Approved :: MIT License&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;Operating System :: OS Independent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.11&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.12&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.13&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;Typing :: Typed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c"># Core dependencies (minimal)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c"># YAML support</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;PyYAML&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c"># Development dependencies</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="nx">dev</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="s2">&#34;pytest-cov&gt;=4.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="s2">&#34;mypy&gt;=1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c"># All optional features</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude-hooks-lib[yaml]&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="nx">Homepage</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="nx">Documentation</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib#readme&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="nx">Repository</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib.git&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="nx">Changelog</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib/blob/main/CHANGELOG.md&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="c"># Command-line entry points</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="nx">hook-validator</span> <span class="p">=</span> <span class="s2">&#34;claude_hooks_lib.hook_validator:main&#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">sdist</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="nx">include</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="s2">&#34;/src&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="s2">&#34;/tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/claude_hooks_lib&#34;</span><span class="p">]</span></span></span></code></pre></div><h5 id="關鍵設定說明">關鍵設定說明</h5>
<ol>
<li><strong>build-system</strong>：使用 Hatch 作為建構後端（現代、快速）</li>
<li><strong>requires-python</strong>：指定最低 Python 版本</li>
<li><strong>dependencies</strong>：核心相依性保持為空（僅使用標準庫）</li>
<li><strong>optional-dependencies</strong>：將 PyYAML 設為可選</li>
<li><strong>project.scripts</strong>：定義命令列工具入口點</li>
</ol>
<h4 id="步驟-3處理相依性">步驟 3：處理相依性</h4>
<p>相依性分層策略：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># 核心相依性：僅標準庫</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c"># 功能性相依性（用戶根據需求安裝）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;PyYAML&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c"># 開發相依性（僅開發者需要）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">dev</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="s2">&#34;pytest-cov&gt;=4.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;mypy&gt;=1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c"># 測試相依性（CI/CD 需要）</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nx">test</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;pytest-cov&gt;=4.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c"># 文件相依性</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nx">docs</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;mkdocs&gt;=1.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;mkdocs-material&gt;=9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c"># 完整安裝</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="s2">&#34;claude-hooks-lib[yaml,dev,docs]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><h5 id="安裝方式範例">安裝方式範例</h5>





<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"><span class="c1"># 基本安裝（無可選相依性）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">pip install claude-hooks-lib
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 包含 YAML 支援</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">pip install <span class="s2">&#34;claude-hooks-lib[yaml]&#34;</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"># 開發者安裝</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">pip install -e <span class="s2">&#34;.[dev]&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 完整安裝</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">pip install <span class="s2">&#34;claude-hooks-lib[all]&#34;</span></span></span></code></pre></div><h4 id="步驟-4版本管理策略">步驟 4：版本管理策略</h4>
<h5 id="方法-a單一來源版本推薦">方法 A：單一來源版本（推薦）</h5>
<p>在 <code>__init__.py</code> 中定義版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># src/claude_hooks_lib/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Claude Hooks Library
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">Shared utilities for building Claude Code hooks.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.28.0&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># Version</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;__version__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># git_utils</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;run_git_command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;get_project_root&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;get_worktree_list&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;is_protected_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;is_allowed_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># hook_logging</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;setup_hook_logging&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># hook_io</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;read_hook_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="s2">&#34;write_hook_output&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;create_pretooluse_output&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;create_posttooluse_output&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># config_loader</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;load_config&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;load_agents_config&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="s2">&#34;load_quality_rules&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;clear_config_cache&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">create_posttooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="kn">from</span> <span class="nn">.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">load_quality_rules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">clear_config_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>在 <code>pyproject.toml</code> 中使用動態版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;claude-hooks-lib&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">dynamic</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;version&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/claude_hooks_lib/__init__.py&#34;</span></span></span></code></pre></div><h5 id="方法-b使用-hatch-vcsgit-tag-版本">方法 B：使用 hatch-vcs（Git tag 版本）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">,</span> <span class="s2">&#34;hatch-vcs&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">dynamic</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;version&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;vcs&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">vcs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">version-file</span> <span class="p">=</span> <span class="s2">&#34;src/claude_hooks_lib/_version.py&#34;</span></span></span></code></pre></div>




<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"><span class="c1"># Create version tag</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">git tag v0.28.0
</span></span><span class="line"><span class="ln">3</span><span class="cl">git push --tags</span></span></code></pre></div><h4 id="步驟-5建立發布流程">步驟 5：建立發布流程</h4>
<p><strong>.github/workflows/publish.yml</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish to PyPI</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">release</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">published]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">id-token</span><span class="p">:</span><span class="w"> </span><span class="l">write </span><span class="w"> </span><span class="c"># Required for trusted publishing</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Python</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-python@v5</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">          </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.12&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install build tools</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="sd">          python -m pip install --upgrade pip
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="sd">          pip install build</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build package</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">python -m build</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload artifacts</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-artifact@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dist</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">dist/</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">  </span><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">    </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">      </span><span class="nt">matrix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">        </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;3.10&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.11&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.12&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.13&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Python ${{ matrix.python-version }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-python@v5</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">          </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="l">${{ matrix.python-version }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install dependencies</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="sd">          python -m pip install --upgrade pip
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="sd">          pip install -e &#34;.[dev,yaml]&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run tests</span><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pytest tests/ -v --cov=src/claude_hooks_lib</span><span class="w">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Type check</span><span class="w">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mypy src/claude_hooks_lib</span><span class="w">
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="w">  </span><span class="nt">publish-testpypi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="w">    </span><span class="nt">needs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">build, test]</span><span class="w">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w"> </span><span class="l">testpypi</span><span class="w">
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Download artifacts</span><span class="w">
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/download-artifact@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dist</span><span class="w">
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">dist/</span><span class="w">
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish to TestPyPI</span><span class="w">
</span></span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">pypa/gh-action-pypi-publish@release/v1</span><span class="w">
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="w">          </span><span class="nt">repository-url</span><span class="p">:</span><span class="w"> </span><span class="l">https://test.pypi.org/legacy/</span><span class="w">
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="w">  </span><span class="nt">publish-pypi</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="w">    </span><span class="nt">needs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">publish-testpypi]</span><span class="w">
</span></span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w"> </span><span class="l">pypi</span><span class="w">
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Download artifacts</span><span class="w">
</span></span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/download-artifact@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">84</span><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">dist</span><span class="w">
</span></span></span><span class="line"><span class="ln">85</span><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">dist/</span><span class="w">
</span></span></span><span class="line"><span class="ln">86</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">87</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish to PyPI</span><span class="w">
</span></span></span><span class="line"><span class="ln">88</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">pypa/gh-action-pypi-publish@release/v1</span></span></span></code></pre></div><p><strong>CI 測試工作流程（.github/workflows/test.yml）</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Tests</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">${{ matrix.os }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">      </span><span class="nt">fail-fast</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">      </span><span class="nt">matrix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="nt">os</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">ubuntu-latest, macos-latest, windows-latest]</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;3.10&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.11&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.12&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.13&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up Python ${{ matrix.python-version }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-python@v5</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">          </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="l">${{ matrix.python-version }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install dependencies</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="sd">          python -m pip install --upgrade pip
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="sd">          pip install -e &#34;.[dev,yaml]&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Lint with ruff</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">ruff check src/ tests/</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Format check with ruff</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">ruff format --check src/ tests/</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Type check with mypy</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mypy src/claude_hooks_lib</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run tests with coverage</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="sd">          pytest tests/ -v --cov=src/claude_hooks_lib --cov-report=xml</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload coverage</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">codecov/codecov-action@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">          </span><span class="nt">files</span><span class="p">:</span><span class="w"> </span><span class="l">./coverage.xml</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<h4 id="完整的-pyprojecttoml">完整的 pyproject.toml</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">  1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;claude-hooks-lib&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="nx">dynamic</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;version&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Shared utilities for Claude Code hooks&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="nx">readme</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="nx">license</span> <span class="p">=</span> <span class="s2">&#34;MIT&#34;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="nx">authors</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="p">{</span> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Your Name&#34;</span><span class="p">,</span> <span class="nx">email</span> <span class="p">=</span> <span class="s2">&#34;your.email@example.com&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="nx">keywords</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude&#34;</span><span class="p">,</span> <span class="s2">&#34;hooks&#34;</span><span class="p">,</span> <span class="s2">&#34;utilities&#34;</span><span class="p">,</span> <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="s2">&#34;automation&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="nx">classifiers</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;Development Status :: 4 - Beta&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;Intended Audience :: Developers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;License :: OSI Approved :: MIT License&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="s2">&#34;Operating System :: OS Independent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.11&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.12&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.13&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;Typing :: Typed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;Topic :: Software Development :: Libraries :: Python Modules&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;PyYAML&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="nx">dev</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;pytest-cov&gt;=4.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="s2">&#34;mypy&gt;=1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="s2">&#34;PyYAML&gt;=6.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="nx">docs</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="s2">&#34;mkdocs&gt;=1.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;mkdocs-material&gt;=9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude-hooks-lib[yaml,dev,docs]&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="nx">Homepage</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib&#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="nx">Documentation</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib#readme&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="nx">Repository</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib.git&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="nx">Changelog</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/claude-hooks-lib/blob/main/CHANGELOG.md&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="nx">hook-validator</span> <span class="p">=</span> <span class="s2">&#34;claude_hooks_lib.hook_validator:main&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="c"># ===== Build Configuration =====</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/claude_hooks_lib/__init__.py&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">sdist</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="nx">include</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;/src&#34;</span><span class="p">,</span> <span class="s2">&#34;/tests&#34;</span><span class="p">,</span> <span class="s2">&#34;/README.md&#34;</span><span class="p">,</span> <span class="s2">&#34;/LICENSE&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/claude_hooks_lib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="c"># ===== Tool Configuration =====</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="nx">src</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="nx">line-length</span> <span class="p">=</span> <span class="mi">88</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="nx">target-version</span> <span class="p">=</span> <span class="s2">&#34;py310&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">.</span><span class="nx">lint</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="nx">select</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;E&#34;</span><span class="p">,</span>      <span class="c"># pycodestyle errors</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="s2">&#34;W&#34;</span><span class="p">,</span>      <span class="c"># pycodestyle warnings</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="s2">&#34;F&#34;</span><span class="p">,</span>      <span class="c"># pyflakes</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;I&#34;</span><span class="p">,</span>      <span class="c"># isort</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="s2">&#34;B&#34;</span><span class="p">,</span>      <span class="c"># flake8-bugbear</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="s2">&#34;C4&#34;</span><span class="p">,</span>     <span class="c"># flake8-comprehensions</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="s2">&#34;UP&#34;</span><span class="p">,</span>     <span class="c"># pyupgrade</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="nx">ignore</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;E501&#34;</span><span class="p">]</span>  <span class="c"># Line too long (handled by formatter)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">.</span><span class="nx">lint</span><span class="p">.</span><span class="nx">isort</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="nx">known-first-party</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;claude_hooks_lib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">mypy</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="nx">python_version</span> <span class="p">=</span> <span class="s2">&#34;3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="nx">warn_return_any</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="nx">warn_unused_ignores</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="nx">disallow_untyped_defs</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="nx">strict</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="p">[[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">mypy</span><span class="p">.</span><span class="nx">overrides</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="nx">module</span> <span class="p">=</span> <span class="s2">&#34;yaml&#34;</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="nx">ignore_missing_imports</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">pytest</span><span class="p">.</span><span class="nx">ini_options</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="nx">testpaths</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;tests&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="nx">python_files</span> <span class="p">=</span> <span class="s2">&#34;test_*.py&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="nx">python_functions</span> <span class="p">=</span> <span class="s2">&#34;test_*&#34;</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="nx">addopts</span> <span class="p">=</span> <span class="s2">&#34;-v --tb=short&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">coverage</span><span class="p">.</span><span class="nx">run</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="nx">source</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/claude_hooks_lib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="nx">branch</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">coverage</span><span class="p">.</span><span class="nx">report</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="nx">exclude_lines</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="s2">&#34;pragma: no cover&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="s2">&#34;if TYPE_CHECKING:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;raise NotImplementedError&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="安裝套件">安裝套件</h4>





<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"><span class="c1"># From PyPI (after publishing)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">pip install claude-hooks-lib
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># With YAML support</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">pip install <span class="s2">&#34;claude-hooks-lib[yaml]&#34;</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"># Development installation (from source)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">git clone https://github.com/yourname/claude-hooks-lib
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">cd</span> claude-hooks-lib
</span></span><span class="line"><span class="ln">10</span><span class="cl">pip install -e <span class="s2">&#34;.[dev]&#34;</span></span></span></code></pre></div><h5 id="python-使用範例">Python 使用範例</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Basic usage</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">claude_hooks_lib</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">setup_hook_logging</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># Initialize logging</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">logger</span> <span class="o">=</span> <span class="n">setup_hook_logging</span><span class="p">(</span><span class="s2">&#34;my-custom-hook&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Check branch protection</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">if</span> <span class="n">branch</span> <span class="ow">and</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Operating on protected branch: </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># Process hook input</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">input_data</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">tool_name</span> <span class="o">=</span> <span class="n">input_data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># Generate output</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">output</span> <span class="o">=</span> <span class="n">create_pretooluse_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">reason</span><span class="o">=</span><span class="s2">&#34;All checks passed&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="n">write_hook_output</span><span class="p">(</span><span class="n">output</span><span class="p">)</span></span></span></code></pre></div><h4 id="命令列工具使用">命令列工具使用</h4>





<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"><span class="c1"># Validate a single hook</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">hook-validator .claude/hooks/my-hook.py
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Validate all hooks</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">hook-validator --all
</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"># Output as JSON</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">hook-validator --all --json
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># Strict mode (warnings as errors)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">hook-validator --all --strict</span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>內部目錄</th>
          <th>獨立套件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>重用性</strong></td>
          <td>僅限單專案</td>
          <td>跨專案共用</td>
      </tr>
      <tr>
          <td><strong>版本管理</strong></td>
          <td>與專案綁定</td>
          <td>獨立語意化版本</td>
      </tr>
      <tr>
          <td><strong>維護成本</strong></td>
          <td>低（無發布流程）</td>
          <td>中（需維護 CI/CD）</td>
      </tr>
      <tr>
          <td><strong>相依管理</strong></td>
          <td>隱式（需手動追蹤）</td>
          <td>顯式（pyproject.toml）</td>
      </tr>
      <tr>
          <td><strong>安裝方式</strong></td>
          <td>複製程式碼或 sys.path</td>
          <td>pip install</td>
      </tr>
      <tr>
          <td><strong>測試隔離</strong></td>
          <td>可能測試到本地版本</td>
          <td>強制測試安裝版本</td>
      </tr>
      <tr>
          <td><strong>API 穩定性</strong></td>
          <td>無保證</td>
          <td>版本號約束</td>
      </tr>
  </tbody>
</table>
<h3 id="專案結構比較">專案結構比較</h3>
<table>
  <thead>
      <tr>
          <th>Layout</th>
          <th>適用場景</th>
          <th>優點</th>
          <th>缺點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Flat Layout</strong></td>
          <td>簡單應用、腳本</td>
          <td>簡單直覺</td>
          <td>測試可能導入錯誤版本</td>
      </tr>
      <tr>
          <td><strong>Src Layout</strong></td>
          <td>函式庫、套件發布</td>
          <td>測試隔離、明確邊界</td>
          <td>額外一層目錄</td>
      </tr>
  </tbody>
</table>
<h3 id="建構工具比較">建構工具比較</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>特點</th>
          <th>推薦場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>setuptools</strong></td>
          <td>成熟穩定、生態最大</td>
          <td>需要相容舊專案</td>
      </tr>
      <tr>
          <td><strong>Hatch</strong></td>
          <td>現代、快速、功能完整</td>
          <td>新專案首選</td>
      </tr>
      <tr>
          <td><strong>Poetry</strong></td>
          <td>依賴鎖定、虛擬環境管理</td>
          <td>需要嚴格依賴控制</td>
      </tr>
      <tr>
          <td><strong>Flit</strong></td>
          <td>極簡、僅純 Python</td>
          <td>簡單函式庫</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該打包成套件">什麼時候該打包成套件？</h2>
<h3 id="適合打包">適合打包</h3>
<ul>
<li>多個專案需要使用相同程式碼</li>
<li>程式碼相對穩定，API 不常變動</li>
<li>需要版本控制和變更追蹤</li>
<li>希望其他人能 pip install 使用</li>
<li>需要明確的相依性管理</li>
</ul>
<h3 id="不建議打包">不建議打包</h3>
<ul>
<li>僅單一專案使用</li>
<li>程式碼還在快速迭代</li>
<li>與專案緊密耦合（如特定的配置路徑）</li>
<li>維護成本超過重用收益</li>
</ul>
<h3 id="決策流程圖">決策流程圖</h3>





<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">程式碼需要跨專案使用？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 否 → 保持內部目錄
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── 是 → API 穩定嗎？
</span></span><span class="line"><span class="ln">4</span><span class="cl">         ├── 否 → 等待穩定後再打包
</span></span><span class="line"><span class="ln">5</span><span class="cl">         └── 是 → 有維護能力嗎？
</span></span><span class="line"><span class="ln">6</span><span class="cl">                  ├── 否 → 考慮 monorepo
</span></span><span class="line"><span class="ln">7</span><span class="cl">                  └── 是 → 打包發布</span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習建立最小的-pyprojecttoml">基礎練習：建立最小的 pyproject.toml</h3>
<p><strong>目標</strong>：為一個簡單的工具函式庫建立 pyproject.toml</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># src/my_utils/__init__.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="s2">&#34;&#34;&#34;Simple utilities.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Return a greeting message.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!&#34;</span></span></span></code></pre></div><p><strong>要求</strong>：</p>
<ol>
<li>使用 hatchling 作為建構後端</li>
<li>設定專案名稱、版本、描述</li>
<li>指定 Python 版本要求（&gt;=3.10）</li>
</ol>
<details>
<summary>參考答案</summary>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-utils&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Simple utility functions&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/my_utils&#34;</span><span class="p">]</span></span></span></code></pre></div></details>
<h3 id="進階練習新增-optional-dependencies">進階練習：新增 optional dependencies</h3>
<p><strong>目標</strong>：擴展上面的套件，加入可選的功能</p>
<p><strong>要求</strong>：</p>
<ol>
<li>新增一個需要 <code>requests</code> 的函式</li>
<li>將 <code>requests</code> 設為可選相依性</li>
<li>加入開發相依性（pytest, ruff）</li>
</ol>
<details>
<summary>參考答案</summary>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-utils&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Simple utility functions&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">http</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;requests&gt;=2.28&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">dev</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;my-utils[http]&#34;</span><span class="p">,</span>  <span class="c"># Include http for testing</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/my_utils&#34;</span><span class="p">]</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># src/my_utils/http.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;HTTP utilities (requires requests).&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">HAS_REQUESTS</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">HAS_REQUESTS</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">fetch_json</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fetch JSON from URL.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">HAS_REQUESTS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s2">&#34;requests is required for this feature. &#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="s2">&#34;Install with: pip install my-utils[http]&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span></span></span></code></pre></div></details>
<h3 id="挑戰題設定-github-actions-自動發布到-pypi">挑戰題：設定 GitHub Actions 自動發布到 PyPI</h3>
<p><strong>目標</strong>：建立完整的 CI/CD 流程</p>
<p><strong>要求</strong>：</p>
<ol>
<li>Pull Request 時執行測試</li>
<li>建立 Release 時自動發布到 PyPI</li>
<li>使用 Trusted Publishing（不需要 API Token）</li>
<li>多版本 Python 測試矩陣</li>
</ol>
<p><strong>提示</strong>：</p>
<ul>
<li>需要在 PyPI 設定 Trusted Publisher</li>
<li>使用 <code>pypa/gh-action-pypi-publish@release/v1</code></li>
<li>設定 <code>id-token: write</code> 權限</li>
</ul>
<details>
<summary>參考答案</summary>
<ol>
<li>
<p>先在 PyPI 設定 Trusted Publisher：</p>
<ul>
<li>前往 <a href="https://pypi.org/manage/account/publishing/">pypi.org</a></li>
<li>新增 GitHub publisher</li>
<li>填入 repository owner、name、workflow 路徑</li>
</ul>
</li>
<li>
<p>建立工作流程檔案：</p>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .github/workflows/ci.yml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">CI</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">  </span><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">      </span><span class="nt">matrix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;3.10&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.11&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.12&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;3.13&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-python@v5</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">          </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="l">${{ matrix.python-version }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pip install -e &#34;.[dev]&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">ruff check src/ tests/</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pytest tests/ -v</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># .github/workflows/publish.yml</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">release</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">published]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">id-token</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="nt">publish</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w"> </span><span class="l">pypi</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-python@v5</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">          </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.12&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">pip install build</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">python -m build</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">pypa/gh-action-pypi-publish@release/v1</span></span></span></code></pre></div></details>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://packaging.python.org/">Python 打包使用者指南</a></li>
<li><a href="https://peps.python.org/pep-0621/">pyproject.toml 規範 (PEP 621)</a></li>
<li><a href="https://hatch.pypa.io/">Hatch 建置工具</a></li>
<li><a href="https://docs.pypi.org/trusted-publishers/">Trusted Publishers (PyPI)</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">模組六：打包與發布</a></p>
]]></content:encoded></item><item><title>案例：快取生命週期管理</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/config_loader.py&lt;/code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>config_loader.py&lt;/code> 使用全域變數實現快取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 全域快取變數&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">_agents_config_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&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">_quality_rules_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入代理人配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用全域快取避免重複讀取檔案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_config_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除配置快取（用於測試或配置熱更新）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：全域變數是最簡單的快取方式&lt;/li>
&lt;li>&lt;strong>效能好&lt;/strong>：配置只讀取一次&lt;/li>
&lt;li>&lt;strong>API 簡潔&lt;/strong>：呼叫者不需要管理快取&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;h4 id="問題-1測試難以隔離">問題 1：測試難以隔離&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config&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="c1"># 測試 A：預設配置&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config_custom&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試 B：自訂配置檔案&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"># 問題：測試 A 的快取會影響測試 B！&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="c1"># 可能拿到測試 A 的快取結果&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2快取生命週期不明確">問題 2：快取生命週期不明確&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_hooks&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="c1"># ... 處理完成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：什麼時候應該清除快取？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 如果配置檔案改了，這裡會用到舊的快取&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3清除快取容易忘記">問題 3：清除快取容易忘記&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_something&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="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="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/test&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 下一個測試會用到這個測試的快取&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>明確的快取範圍&lt;/strong>：快取的生命週期有明確的開始和結束&lt;/li>
&lt;li>&lt;strong>自動清理&lt;/strong>：離開範圍時自動清除快取&lt;/li>
&lt;li>&lt;strong>測試友好&lt;/strong>：每個測試可以有獨立的快取&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立快取管理類別">步驟 1：建立快取管理類別&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用 Context Manager 控制快取的生命週期，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解決全域快取的問題。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> 初始化配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 專案根目錄，預設從環境變數讀取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">config_dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置目錄路徑&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入配置（使用快取）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2"> name: 配置名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;從檔案載入配置（內部方法）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">yaml_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除所有快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&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="c1"># ... 前面的程式碼 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cached_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 快取範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 在這個範圍內，配置會被快取。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 離開範圍時自動清除快取。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 自己，方便鏈式呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> manager = ConfigManager()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> with manager.cached_scope():
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> config = manager.load_config(&amp;#34;agents&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> # ... 使用配置 ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 離開時快取自動清除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 隔離範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立一個完全隔離的配置環境，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2"> 適合用於測試。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 臨時的專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 新的管理器實例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> with manager.isolated_scope(&amp;#34;/tmp/test&amp;#34;) as isolated:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2"> config = isolated.load_config(&amp;#34;agents&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 完全隔離，不影響原本的 manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-3加入便利方法">步驟 3：加入便利方法&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&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="c1"># ... 前面的程式碼 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;載入代理人配置（帶預設值）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&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="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_get_default_agents_config&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&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="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置（帶預設值）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_get_default_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_get_default_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;預設代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;basil&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;thyme&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mint&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;agent_dispatch_rules&amp;#34;&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">23&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_get_default_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;預設品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;allowed_tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Edit&amp;#34;&lt;/span>&lt;span class="p">]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整程式碼">完整程式碼&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="ch">#!/usr/bin/env python3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">快取生命週期管理 - 完整範例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">展示如何用 Context Manager 管理配置快取的生命週期。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用 Context Manager 控制快取的生命週期。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">config_dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== 載入方法 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入配置（使用快取）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_load_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;從檔案載入配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="n">json_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">yaml_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">HAS_YAML&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">json_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除所有快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== Context Manager =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cached_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;快取範圍：離開時自動清除快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;隔離範圍：建立獨立的配置環境&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="n">isolated&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== 便利方法 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[],&lt;/span> &lt;span class="s2">&amp;#34;agent_dispatch_rules&amp;#34;&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"> 94&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{},&lt;/span> &lt;span class="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 全域便利函式（相容舊 API）=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl">&lt;span class="n">_default_manager&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取預設的配置管理器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_default_manager&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl"> &lt;span class="n">_default_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl">&lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">config_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立一個有明確生命週期的配置環境。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl">&lt;span class="s2"> with config_scope(&amp;#34;/path/to/project&amp;#34;) as config:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl">&lt;span class="s2"> agents = config.load_agents_config()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl">&lt;span class="s2"> rules = config.load_quality_rules()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 離開時快取自動清除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 測試範例 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">147&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">153&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 寫入測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent-1&amp;#34;, &amp;#34;test-agent-2&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用 Context Manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">161&lt;/span>&lt;span class="cl"> &lt;span class="n">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">162&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;代理人配置: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">agents&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">163&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">164&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次呼叫會使用快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">165&lt;/span>&lt;span class="cl"> &lt;span class="n">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">166&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;快取命中: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">167&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">168&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 離開範圍後快取已清除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">169&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;離開範圍，快取已清除&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">170&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">171&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試隔離範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">172&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">=== 測試隔離範圍 ===&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">173&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">174&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">175&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">176&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 隔離環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">177&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">178&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;nonexistent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">179&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">180&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;預期的錯誤: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">181&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">182&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;隔離範圍結束&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用範例">使用範例&lt;/h3>
&lt;h4 id="基本使用">基本使用&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 建立配置管理器&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">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用快取範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&lt;/span>&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 class="n">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">rules&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次呼叫會使用快取&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">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="n">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&lt;/span> &lt;span class="c1"># 同一個物件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 離開範圍後快取已清除&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="測試中使用">測試中使用&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@pytest.fixture&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">config_manager&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試用的配置管理器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&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="c1"># 準備測試配置&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">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&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 class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用隔離範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;test-agent&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_cache_works&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測試快取功能&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">config1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">config2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">config1&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">config2&lt;/span> &lt;span class="c1"># 同一個物件&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="相容舊-api">相容舊 API&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 如果有既有程式碼使用舊 API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">config_loader&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_scope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 舊的呼叫方式仍然可用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&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"># 新的呼叫方式有明確的生命週期&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">manager&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計權衡">設計權衡&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>全域快取&lt;/th>
 &lt;th>Context Manager&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>簡單性&lt;/td>
 &lt;td>最簡單&lt;/td>
 &lt;td>需要理解 Context Manager&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;tr>
 &lt;td>API 相容性&lt;/td>
 &lt;td>N/A&lt;/td>
 &lt;td>可以相容舊 API&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/config_loader.py</code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>config_loader.py</code> 使用全域變數實現快取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 全域快取變數</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_quality_rules_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    使用全域快取避免重複讀取檔案。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">global</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">_quality_rules_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;清除配置快取（用於測試或配置熱更新）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span><span class="p">,</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：全域變數是最簡單的快取方式</li>
<li><strong>效能好</strong>：配置只讀取一次</li>
<li><strong>API 簡潔</strong>：呼叫者不需要管理快取</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<h4 id="問題-1測試難以隔離">問題 1：測試難以隔離</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># 測試 A：預設配置</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">assert</span> <span class="s2">&#34;known_agents&#34;</span> <span class="ow">in</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config_custom</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 測試 B：自訂配置檔案</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 問題：測試 A 的快取會影響測試 B！</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 可能拿到測試 A 的快取結果</span></span></span></code></pre></div><h4 id="問題-2快取生命週期不明確">問題 2：快取生命週期不明確</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">process_hooks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</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></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="c1"># 問題：什麼時候應該清除快取？</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1"># 如果配置檔案改了，這裡會用到舊的快取</span></span></span></code></pre></div><h4 id="問題-3清除快取容易忘記">問題 3：清除快取容易忘記</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">test_something</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 設定測試環境</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;/tmp/test&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="c1"># 執行測試</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="c1"># 問題：忘記清除快取！</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="c1"># 下一個測試會用到這個測試的快取</span></span></span></code></pre></div><h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>明確的快取範圍</strong>：快取的生命週期有明確的開始和結束</li>
<li><strong>自動清理</strong>：離開範圍時自動清除快取</li>
<li><strong>測試友好</strong>：每個測試可以有獨立的快取</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立快取管理類別">步驟 1：建立快取管理類別</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    配置管理器
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    用 Context Manager 控制快取的生命週期，
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    解決全域快取的問題。
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        初始化配置管理器
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            project_root: 專案根目錄，預設從環境變數讀取
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;配置目錄路徑&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        載入配置（使用快取）
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">            name: 配置名稱
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            配置內容
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置（內部方法）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span></span></span></code></pre></div><h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># ... 前面的程式碼 ...</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        快取範圍 Context Manager
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        在這個範圍內，配置會被快取。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        離開範圍時自動清除快取。
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        Yields:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            ConfigManager: 自己，方便鏈式呼叫
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">            manager = ConfigManager()
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            with manager.cached_scope():
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">                config = manager.load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">                # ... 使用配置 ...
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            # 離開時快取自動清除
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        隔離範圍 Context Manager
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        建立一個完全隔離的配置環境，
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">        適合用於測試。
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            project_root: 臨時的專案根目錄
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        Yields:
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">            ConfigManager: 新的管理器實例
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">            with manager.isolated_scope(&#34;/tmp/test&#34;) as isolated:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">                config = isolated.load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">                # 完全隔離，不影響原本的 manager
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">isolated_manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="n">project_root</span> <span class="ow">or</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated_manager</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="n">isolated_manager</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span></span></span></code></pre></div><h4 id="步驟-3加入便利方法">步驟 3：加入便利方法</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># ... 前面的程式碼 ...</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入代理人配置（帶預設值）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入品質規則配置（帶預設值）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_default_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="s2">&#34;&#34;&#34;預設代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;basil&#34;</span><span class="p">,</span> <span class="s2">&#34;thyme&#34;</span><span class="p">,</span> <span class="s2">&#34;mint&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_default_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;預設品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;allowed_tools&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Write&#34;</span><span class="p">,</span> <span class="s2">&#34;Edit&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="p">}</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">快取生命週期管理 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 Context Manager 管理配置快取的生命週期。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    配置管理器
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    用 Context Manager 控制快取的生命週期。
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="c1"># ===== 載入方法 =====</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入配置（使用快取）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">json_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.json&#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">if</span> <span class="n">json_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">json_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1"># ===== Context Manager =====</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="s2">&#34;&#34;&#34;快取範圍：離開時自動清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;隔離範圍：建立獨立的配置環境&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="n">isolated</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span> <span class="ow">or</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="n">isolated</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="c1"># ===== 便利方法 =====</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</span> <span class="p">{}}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">}}</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="c1"># ===== 全域便利函式（相容舊 API）=====</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="n">_default_manager</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="k">def</span> <span class="nf">get_config_manager</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取預設的配置管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">global</span> <span class="n">_default_manager</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">if</span> <span class="n">_default_manager</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">_default_manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="k">return</span> <span class="n">_default_manager</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="k">def</span> <span class="nf">config_scope</span><span class="p">(</span><span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">    配置範圍 Context Manager
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">    建立一個有明確生命週期的配置環境。
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">        project_root: 專案根目錄
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">    Yields:
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        ConfigManager: 配置管理器
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">        with config_scope(&#34;/path/to/project&#34;) as config:
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="s2">            agents = config.load_agents_config()
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="s2">            rules = config.load_quality_rules()
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="s2">        # 離開時快取自動清除
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">yield</span> <span class="n">manager</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="c1"># ===== 測試範例 =====</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="c1"># 建立測試配置</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="c1"># 寫入測試配置</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent-1&#34;, &#34;test-agent-2&#34;]}&#39;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="c1"># 使用 Context Manager</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">with</span> <span class="n">config_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;代理人配置: </span><span class="si">{</span><span class="n">agents</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="c1"># 第二次呼叫會使用快取</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">            <span class="n">agents2</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取命中: </span><span class="si">{</span><span class="n">agents</span> <span class="ow">is</span> <span class="n">agents2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="c1"># 離開範圍後快取已清除</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;離開範圍，快取已清除&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="c1"># 測試隔離範圍</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== 測試隔離範圍 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="c1"># 隔離環境</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">            <span class="n">config</span> <span class="o">=</span> <span class="n">isolated</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;nonexistent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預期的錯誤: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;隔離範圍結束&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 建立配置管理器</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 使用快取範圍</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">agents</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">rules</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 第二次呼叫會使用快取</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">agents2</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">agents</span> <span class="ow">is</span> <span class="n">agents2</span>  <span class="c1"># 同一個物件</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 離開範圍後快取已清除</span></span></span></code></pre></div><h4 id="測試中使用">測試中使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@pytest.fixture</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">config_manager</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試用的配置管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="c1"># 準備測試配置</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent&#34;]}&#39;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 使用隔離範圍</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">def</span> <span class="nf">test_load_agents_config</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">assert</span> <span class="n">config</span><span class="p">[</span><span class="s2">&#34;known_agents&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&#34;test-agent&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">def</span> <span class="nf">test_cache_works</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試快取功能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">config1</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">config2</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">assert</span> <span class="n">config1</span> <span class="ow">is</span> <span class="n">config2</span>  <span class="c1"># 同一個物件</span></span></span></code></pre></div><h4 id="相容舊-api">相容舊 API</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 如果有既有程式碼使用舊 API</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">config_loader</span> <span class="kn">import</span> <span class="n">load_agents_config</span><span class="p">,</span> <span class="n">config_scope</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 舊的呼叫方式仍然可用</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><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"># 新的呼叫方式有明確的生命週期</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">with</span> <span class="n">config_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">manager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>全域快取</th>
          <th>Context Manager</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>簡單性</td>
          <td>最簡單</td>
          <td>需要理解 Context Manager</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>
      <tr>
          <td>API 相容性</td>
          <td>N/A</td>
          <td>可以相容舊 API</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要明確的快取生命週期</li>
<li>測試需要隔離的快取</li>
<li>可能並行存取快取</li>
<li>快取資料量大，需要及時釋放</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>快取需要跨多個函式呼叫共享</li>
<li>快取很小，不需要特別管理</li>
<li>程式很簡單，不需要測試隔離</li>
</ul>
<h2 id="進階與-exitstack-結合">進階：與 ExitStack 結合</h2>
<p>當需要管理多個快取時：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">ExitStack</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">process_with_multiple_caches</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 ExitStack 管理多個快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 進入多個快取範圍</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">config_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">db_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">database_cache_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">api_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">api_cache_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 使用各種快取</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">users</span> <span class="o">=</span> <span class="n">db_cache</span><span class="o">.</span><span class="n">get_users</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">api_cache</span><span class="o">.</span><span class="n">fetch_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="n">process</span><span class="p">(</span><span class="n">agents</span><span class="p">,</span> <span class="n">users</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># ExitStack 會自動清理所有快取</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>為 <code>ConfigManager</code> 加入 <code>reload_config</code> 方法，強制重新載入配置</li>
<li>實作一個 <code>@cached_config</code> 裝飾器，讓函式自動使用快取範圍</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>加入 TTL（Time To Live）支援，讓快取自動過期</li>
<li>實作一個執行緒安全的 <code>ConfigManager</code>，支援多執行緒存取</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>參考 <code>config_loader.py</code> 的 Fallback Pattern（YAML → JSON → 預設值），用 Context Manager 實現「配置來源優先級」的管理</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/contextlib.html">contextlib 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/functools.html#functools.lru_cache">functools.lru_cache</a></li>
<li><a href="https://cachetools.readthedocs.io/">cachetools</a> - 更多快取策略</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/" data-link-title="案例研究：設計模式實戰" data-link-desc="基於 Hook 系統的進階設計模式實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">案例：插件架構設計</a></em></p>
]]></content:encoded></item><item><title>案例：並行檔案檢查</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 的 &lt;code>check_directory()&lt;/code> 方法檢查目錄下所有 Markdown 檔案的內部連結：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory&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="bp">self&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">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&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 class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查目錄下所有 Markdown 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> dir_path: 目錄路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> recursive: 是否遞迴檢查子目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[LinkCheckResult]: 所有檔案的檢查結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">total_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">BrokenLink&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">file&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">link_text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">link_target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 循序檢查每個檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，程式碼容易理解&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：檔案按排序順序處理，結果也按順序返回&lt;/li>
&lt;li>&lt;strong>除錯容易&lt;/strong>：問題發生時，可以精確定位到哪個檔案&lt;/li>
&lt;/ol>
&lt;h3 id="效能瓶頸分析">效能瓶頸分析&lt;/h3>
&lt;p>讓我們分析 &lt;code>check_file()&lt;/code> 方法的執行時間組成：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">LinkCheckResult&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="s2">&amp;#34;&amp;#34;&amp;#34;檢查單個 Markdown 檔案的連結&amp;#34;&amp;#34;&amp;#34;&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">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 1. 檢查檔案是否存在（I/O）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">try&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="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&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="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 3. 解析連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 4. 過濾內部連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">internal_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_filter_internal_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">links&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">link&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">internal_links&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">suggestion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_check_link&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">link&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&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;/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">操作 | 類型 | 每檔案耗時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">read_text() | I/O | 1-5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">parse_links() | CPU | 0.1 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">filter_links() | CPU | 0.01 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">check_link() x N | I/O | N * 0.5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">總計（10 連結） | | ~7 ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>對於 100 個檔案的專案：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 的 <code>check_directory()</code> 方法檢查目錄下所有 Markdown 檔案的內部連結：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1"># 循序檢查每個檔案</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：循序執行，程式碼容易理解</li>
<li><strong>結果有序</strong>：檔案按排序順序處理，結果也按順序返回</li>
<li><strong>除錯容易</strong>：問題發生時，可以精確定位到哪個檔案</li>
</ol>
<h3 id="效能瓶頸分析">效能瓶頸分析</h3>
<p>讓我們分析 <code>check_file()</code> 方法的執行時間組成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查單個 Markdown 檔案的連結&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 1. 檢查檔案是否存在（I/O）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># 3. 解析連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 4. 過濾內部連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span></span></span></code></pre></div><p><strong>時間分布估計</strong>：</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">操作              | 類型  | 每檔案耗時
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">3</span><span class="cl">read_text()      | I/O   | 1-5 ms
</span></span><span class="line"><span class="ln">4</span><span class="cl">parse_links()    | CPU   | 0.1 ms
</span></span><span class="line"><span class="ln">5</span><span class="cl">filter_links()   | CPU   | 0.01 ms
</span></span><span class="line"><span class="ln">6</span><span class="cl">check_link() x N | I/O   | N * 0.5 ms
</span></span><span class="line"><span class="ln">7</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">8</span><span class="cl">總計（10 連結）  |       | ~7 ms</span></span></code></pre></div><p>對於 100 個檔案的專案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 循序執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">total_time</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="mi">7</span><span class="n">ms</span> <span class="o">=</span> <span class="mi">700</span><span class="n">ms</span> <span class="o">=</span> <span class="mf">0.7</span> <span class="n">秒</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 這看起來不長，但如果：</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># - 檔案更多（500+ 個）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># - 每個檔案連結更多</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># - 網路檔案系統（NFS）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 時間會快速增長</span></span></span></code></pre></div><h4 id="為什麼適合並行化">為什麼適合並行化？</h4>
<ol>
<li><strong>I/O 密集</strong>：大部分時間花在檔案讀取和存在性檢查</li>
<li><strong>任務獨立</strong>：每個檔案的檢查互不依賴</li>
<li><strong>無共享狀態</strong>：不需要同步機制</li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>提升效能</strong>：利用並行化加速 I/O 操作</li>
<li><strong>保持 API 相容</strong>：不改變方法簽名和返回值</li>
<li><strong>可配置</strong>：允許調整並行度</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別獨立任務">步驟 1：識別獨立任務</h4>
<p>每個檔案的檢查是完全獨立的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這些操作可以同時執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">result_1</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc1.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">result_2</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc2.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">result_3</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc3.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span></span></span></code></pre></div><h4 id="步驟-2使用-threadpoolexecutor">步驟 2：使用 ThreadPoolExecutor</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        max_workers: 最大工作執行緒數，預設為 CPU 核心數
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_create_error_result</span><span class="p">(</span><span class="n">dir_path</span><span class="p">,</span> <span class="s2">&#34;目錄不存在&#34;</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="s2">&#34;**/*.md&#34;</span> <span class="k">if</span> <span class="n">recursive</span> <span class="k">else</span> <span class="s2">&#34;*.md&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">recursive</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                      <span class="k">else</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># 使用 ThreadPoolExecutor 並行處理</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)):</span> <span class="n">md_file</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># 按檔案路徑排序（保持一致的輸出順序）</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h4 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers</h4>
<p><code>max_workers</code> 的選擇影響效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 預設值：min(32, os.cpu_count() + 4)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 這是 Python 3.8+ 的預設行為</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 對於 I/O 密集任務，可以設定更高</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">get_optimal_workers</span><span class="p">(</span><span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    根據檔案數量計算最佳工作執行緒數
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    經驗法則：
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 檔案數 &lt; 10: 使用檔案數
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 檔案數 &gt;= 10: 使用 CPU 核心數 * 2，但不超過 32
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">基於 markdown_link_checker.py，展示如何用 ThreadPoolExecutor 加速檔案檢查。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">class</span> <span class="nc">BrokenLink</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;&#34;&#34;失效連結描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">link_text</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">link_target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="k">class</span> <span class="nc">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個檔案的連結檢查結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="n">total_links</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">broken_links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">BrokenLink</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">ParallelMarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    相較於原版的改進：
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    - check_directory() 使用 ThreadPoolExecutor 並行處理
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">    - 支援自訂 max_workers
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    - 保持 API 相容性
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">EXTERNAL_PATTERNS</span> <span class="o">=</span> <span class="p">[</span><span class="sa">r</span><span class="s1">&#39;^https?://&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^mailto:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^tel:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^ftp://&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="c1"># ===== 核心方法 =====</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        檢查單個 Markdown 檔案的連結
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">        這個方法是執行緒安全的，可以並行呼叫。
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span> <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;line&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="n">suggestion</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">total_links</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">internal_links</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">broken_links</span><span class="o">=</span><span class="n">broken_links</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="s2">        並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">            dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">            recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">            max_workers: 最大工作執行緒數，None 表示使用預設值
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">            list[LinkCheckResult]: 所有檔案的檢查結果（按路徑排序）
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="c1"># 計算最佳工作執行緒數</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">if</span> <span class="n">max_workers</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">max_workers</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_optimal_workers</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">md_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="c1"># 並行處理</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">                <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="c1"># 收集結果（as_completed 提供最快的回應）</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">            <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">                    <span class="c1"># 處理意外錯誤</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">                    <span class="n">md_file</span> <span class="o">=</span> <span class="n">future_to_file</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">                        <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">                            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                            <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">                            <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">                                <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">                                    <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                                    <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檢查失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">                                <span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">                            <span class="p">]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 排序以保持一致的輸出順序</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="c1"># ===== 循序版本（用於比較）=====</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory_sequential</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="s2">&#34;&#34;&#34;循序版本，用於效能比較&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="c1"># ===== 私有方法 =====</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="k">def</span> <span class="nf">_parse_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">),</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">
</span></span><span class="line"><span class="ln">251</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">
</span></span><span class="line"><span class="ln">261</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="k">def</span> <span class="nf">_filter_internal_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">internal</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">            <span class="n">target</span> <span class="o">=</span> <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">EXTERNAL_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="n">internal</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">return</span> <span class="n">internal</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="n">target</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="n">base_dir</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">target_path</span> <span class="o">=</span> <span class="n">target</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">target_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">resolved</span> <span class="o">=</span> <span class="p">(</span><span class="n">base_dir</span> <span class="o">/</span> <span class="n">target_path</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="k">if</span> <span class="n">resolved</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">target_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">
</span></span><span class="line"><span class="ln">289</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_optimal_workers</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;&#34;&#34;計算最佳工作執行緒數&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="c1"># ===== 效能測量工具 =====</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">
</span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_checker</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">303</span><span class="cl"><span class="s2">    比較循序與並行版本的效能
</span></span></span><span class="line"><span class="ln">304</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">305</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">306</span><span class="cl"><span class="s2">        dir_path: 要檢查的目錄
</span></span></span><span class="line"><span class="ln">307</span><span class="cl"><span class="s2">        iterations: 執行次數（取平均）
</span></span></span><span class="line"><span class="ln">308</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">309</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="s2">        dict: {&#39;sequential&#39;: 秒數, &#39;parallel&#39;: 秒數, &#39;speedup&#39;: 加速比}
</span></span></span><span class="line"><span class="ln">311</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="c1"># 預熱（讓檔案系統快取生效）</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="c1"># 測量循序版本</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">seq_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">seq_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="c1"># 測量並行版本</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">par_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">par_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl">    <span class="n">seq_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="n">par_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="s2">&#34;sequential&#34;</span><span class="p">:</span> <span class="n">seq_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="s2">&#34;parallel&#34;</span><span class="p">:</span> <span class="n">par_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">seq_avg</span> <span class="o">/</span> <span class="n">par_avg</span> <span class="k">if</span> <span class="n">par_avg</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">
</span></span><span class="line"><span class="ln">342</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="c1"># 預設檢查當前目錄</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">target_dir</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;=== 並行 Markdown 連結檢查示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目標目錄: </span><span class="si">{</span><span class="n">target_dir</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 執行檢查</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">
</span></span><span class="line"><span class="ln">358</span><span class="cl">    <span class="c1"># 統計</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">    <span class="n">total_files</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="n">total_links</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">total_links</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">    <span class="n">broken_count</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">invalid_files</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">r</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">
</span></span><span class="line"><span class="ln">364</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;檔案數: </span><span class="si">{</span><span class="n">total_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;連結數: </span><span class="si">{</span><span class="n">total_links</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;失效連結: </span><span class="si">{</span><span class="n">broken_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;有問題的檔案: </span><span class="si">{</span><span class="n">invalid_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">
</span></span><span class="line"><span class="ln">369</span><span class="cl">    <span class="c1"># 顯示失效連結</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">    <span class="k">if</span> <span class="n">broken_count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">失效連結詳情:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">        <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">373</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">  </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">file_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">broken_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    Line </span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">line</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_text</span><span class="si">}</span><span class="s2">](/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_target</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">377</span><span class="cl">
</span></span><span class="line"><span class="ln">378</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">    <span class="k">if</span> <span class="n">total_files</span> <span class="o">&gt;=</span> <span class="mi">5</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== 效能比較 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="n">benchmark</span> <span class="o">=</span> <span class="n">benchmark_checker</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;sequential&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;parallel&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能測量">效能測量</h3>
<p>使用 <code>timeit</code> 比較前後效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">parallel_link_checker</span> <span class="kn">import</span> <span class="n">ParallelMarkdownLinkChecker</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">measure_performance</span><span class="p">(</span><span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">num_runs</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量並比較循序與並行版本的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 循序版本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 並行版本</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">par_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目錄: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">seq_time</span> <span class="o">/</span> <span class="n">par_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># 實際測試結果（範例）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># 目錄: ./docs （50 個 .md 檔案）</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 循序版本: 0.3521 秒</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 並行版本: 0.0892 秒</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 加速比: 3.95x</span></span></span></code></pre></div><p><strong>不同規模的預期加速比</strong>：</p>
<table>
  <thead>
      <tr>
          <th>檔案數</th>
          <th>循序時間</th>
          <th>並行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10</td>
          <td>70 ms</td>
          <td>25 ms</td>
          <td>2.8x</td>
      </tr>
      <tr>
          <td>50</td>
          <td>350 ms</td>
          <td>90 ms</td>
          <td>3.9x</td>
      </tr>
      <tr>
          <td>100</td>
          <td>700 ms</td>
          <td>160 ms</td>
          <td>4.4x</td>
      </tr>
      <tr>
          <td>500</td>
          <td>3.5 s</td>
          <td>750 ms</td>
          <td>4.7x</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>注意：實際加速比取決於檔案大小、連結數量、磁碟速度等因素。</p></blockquote>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>循序版本</th>
          <th>並行版本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>效能</td>
          <td>較慢，線性增長</td>
          <td>快 3-5 倍</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>
      <tr>
          <td>錯誤處理</td>
          <td>直接</td>
          <td>需要處理 Future 例外</td>
      </tr>
  </tbody>
</table>
<h3 id="執行緒安全考量">執行緒安全考量</h3>
<p><code>check_file()</code> 方法是執行緒安全的，因為：</p>
<ol>
<li><strong>無共享狀態</strong>：每次呼叫都獨立處理一個檔案</li>
<li><strong>唯讀操作</strong>：只讀取檔案，不修改</li>
<li><strong>獨立返回值</strong>：每個呼叫返回獨立的 <code>LinkCheckResult</code></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這是安全的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</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">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>  <span class="c1"># 新物件</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>            <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>         <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>                <span class="c1"># 新物件</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>多檔案處理</strong>：需要處理大量獨立檔案</li>
<li><strong>I/O 密集</strong>：主要時間花在檔案讀寫</li>
<li><strong>任務獨立</strong>：每個任務不依賴其他任務的結果</li>
<li><strong>可接受亂序</strong>：或願意在最後排序</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>檔案很少</strong>：少於 5 個檔案，並行開銷可能大於收益</li>
<li><strong>CPU 密集</strong>：如果主要時間花在計算，應考慮 ProcessPoolExecutor</li>
<li><strong>有依賴關係</strong>：後續檔案依賴前面檔案的結果</li>
<li><strong>記憶體受限</strong>：並行版本會同時載入多個檔案</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="練習-1加入進度回報">練習 1：加入進度回報</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">callback</span><span class="p">:</span> <span class="n">callable</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行檢查，並在每個檔案完成時呼叫 callback
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    callback 簽名: callback(completed: int, total: int, result: LinkCheckResult)
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：使用 as_completed() 在每個任務完成時觸發回報
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-2支援取消">練習 2：支援取消</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_cancellable</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cancel_event</span><span class="p">:</span> <span class="n">threading</span><span class="o">.</span><span class="n">Event</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    可取消的並行檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當 cancel_event.is_set() 時，停止提交新任務並返回已完成的結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：在迴圈中檢查 cancel_event
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="練習-3批次處理大型目錄">練習 3：批次處理大型目錄</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_batched</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    分批處理大型目錄
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    避免一次提交太多任務導致記憶體問題
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：將檔案列表分成多個批次，依序處理每批
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-4加入重試機制">練習 4：加入重試機制</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file_with_retry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    帶重試的檔案檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當檔案被鎖定或暫時不可用時自動重試
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：捕捉特定例外，使用指數退避
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="練習-5實作並行度自動調整">練習 5：實作並行度自動調整</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveParallelChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    自動調整並行度的檢查器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    根據系統負載和檢查速度動態調整 max_workers
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    功能：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 初始使用保守的 max_workers
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 如果任務完成很快，增加 max_workers
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 如果系統負載高，減少 max_workers
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 記錄最佳 max_workers 供下次使用
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor">ThreadPoolExecutor 使用指南</a></li>
<li><a href="https://superfastpython.com/threadpoolexecutor-map-vs-submit/">as_completed vs map 的選擇</a></li>
<li>入門系列：<a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></p>
]]></content:encoded></item><item><title>案例：非同步 subprocess</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/git_utils.py&lt;/code> 的實際程式碼，展示如何用 asyncio 實現非同步的外部命令執行。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>git_utils.py&lt;/code> 使用同步的 &lt;code>subprocess.run&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">run_git_command&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="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&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 class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 執行 git 命令並返回結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> args: git 命令參數列表（不含 &amp;#39;git&amp;#39;）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> cwd: 執行目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> timeout: 命令超時時間（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">capture_output&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutExpired&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Command timed out after &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git command not found&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取當前分支名稱&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取所有 worktree 列表&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;worktree&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;list&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--porcelain&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">success&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 解析邏輯&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：同步呼叫，容易理解&lt;/li>
&lt;li>&lt;strong>錯誤處理完善&lt;/strong>：處理超時、檔案不存在等情況&lt;/li>
&lt;li>&lt;strong>API 清晰&lt;/strong>：返回 &lt;code>(bool, str)&lt;/code> 元組&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;h4 id="問題無法並行執行多個-git-命令">問題：無法並行執行多個 Git 命令&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;檢查所有 worktree 的狀態&amp;#34;&amp;#34;&amp;#34;&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">results&lt;/span> &lt;span class="o">=&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="k">for&lt;/span> &lt;span class="n">worktree&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&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="c1"># 每次呼叫都會阻塞等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">success&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">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">worktree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">worktree&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 如果有 10 個 worktree，每個花 0.5 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 總共需要 5 秒！&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題阻塞事件迴圈">問題：阻塞事件迴圈&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">handle_request&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="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="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&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="c1"># 其他協程無法執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">branch&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案非同步-subprocess">進階解決方案：非同步 subprocess&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>非阻塞執行&lt;/strong>：不阻塞事件迴圈&lt;/li>
&lt;li>&lt;strong>並行能力&lt;/strong>：可以同時執行多個命令&lt;/li>
&lt;li>&lt;strong>相容性&lt;/strong>：保持與同步版本相似的 API&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立非同步命令執行器">步驟 1：建立非同步命令執行器&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_run_git_command&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="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&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 class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">10.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 非同步執行 git 命令
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> args: git 命令參數列表（不含 &amp;#39;git&amp;#39;）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> cwd: 執行目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> timeout: 命令超時時間（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> success, output = await async_run_git_command([&amp;#34;status&amp;#34;])
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立非同步子進程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_subprocess_exec&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">stderr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 等待完成（帶超時）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait_for&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">communicate&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kill&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Command timed out after &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 處理結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git command not found&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2建立便利函式">步驟 2：建立便利函式&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_current_branch&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;非同步獲取當前分支名稱&amp;#34;&amp;#34;&amp;#34;&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">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&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="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_project_root&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;非同步獲取專案根目錄&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">os&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">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;rev-parse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-toplevel&amp;#34;&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="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;非同步獲取 worktree 列表&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;worktree&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;list&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--porcelain&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">success&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;worktree &amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_worktree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="p">:]}&lt;/span> &lt;span class="c1"># len(&amp;#34;worktree &amp;#34;) = 9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;branch &amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">branch_ref&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">:]&lt;/span> &lt;span class="c1"># len(&amp;#34;branch &amp;#34;) = 7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">branch_ref&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;refs/heads/&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">branch_ref&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">branch_ref&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">11&lt;/span>&lt;span class="p">:]&lt;/span> &lt;span class="c1"># len(&amp;#34;refs/heads/&amp;#34;) = 11&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">branch_ref&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;detached&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;detached&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_worktree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-3實現並行執行">步驟 3：實現並行執行&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 並行檢查所有 worktree 的狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> worktrees: worktree 路徑列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 狀態} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktree&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&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="s2">&amp;#34;&amp;#34;&amp;#34;檢查單個 worktree&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">worktree&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">worktree&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 並行執行所有檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_all_branches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> 並行獲取所有 worktree 的當前分支
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> worktrees: worktree 路徑列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 分支名} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktree&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">worktree&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">worktree&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">branch&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;unknown&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">get_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整程式碼">完整程式碼&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="ch">#!/usr/bin/env python3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">非同步 Git 操作工具 - 完整範例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">展示如何用 asyncio 實現非阻塞的 Git 命令執行。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 核心功能 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl"> &lt;span class="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">10.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="s2"> 非同步執行 git 命令
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2"> args: git 命令參數列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> cwd: 執行目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">&lt;span class="s2"> timeout: 超時時間（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">&lt;span class="s2"> (是否成功, 輸出或錯誤訊息)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_subprocess_exec&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="n">stderr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait_for&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">communicate&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kill&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Command timed out after &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git command not found&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 便利函式 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_current_branch&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取當前分支&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_project_root&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取專案根目錄&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;rev-parse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-toplevel&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;獲取 worktree 列表&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;worktree&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;list&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--porcelain&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">success&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;worktree &amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_worktree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="p">:]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;branch &amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="n">branch_ref&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">branch_ref&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;refs/heads/&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">branch_ref&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">branch_ref&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">11&lt;/span>&lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">branch_ref&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;detached&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;detached&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_worktree&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_worktree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">worktrees&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 並行操作 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;並行檢查所有 worktree 狀態&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_all_branches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;並行獲取所有 worktree 的分支&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">branch&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;unknown&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">get_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">batch_git_commands&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="n">commands&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl">&lt;span class="s2"> 批次執行多個 Git 命令
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">&lt;span class="s2"> commands: [(args, cwd), ...] 命令列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl">&lt;span class="s2"> [(success, output), ...] 結果列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl"> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cwd&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">commands&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 同步/非同步橋接 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl"> &lt;span class="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">147&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">10.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本（相容舊 API）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl">&lt;span class="s2"> 在已有事件迴圈的環境中，這會建立新的迴圈執行。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">153&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;同步版本：獲取當前分支&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_get_current_branch&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 測試 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">161&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">162&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">demo&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">163&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;示範非同步 Git 操作&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">164&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== 非同步 Git 操作示範 ===&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">165&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">166&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 單一命令&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">167&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;1. 獲取當前分支:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">168&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_current_branch&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">169&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 分支: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">170&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">171&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 獲取專案根目錄&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">172&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;2. 獲取專案根目錄:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">173&lt;/span>&lt;span class="cl"> &lt;span class="n">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_project_root&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">174&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 根目錄: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">root&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">175&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">176&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 獲取 worktree 列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">177&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;3. 獲取 worktree 列表:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">178&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">179&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">180&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">wt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;detached&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">181&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; - &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;path&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">182&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">183&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">184&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 如果有多個 worktree，示範並行操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">185&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">186&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;4. 並行檢查所有 worktree 狀態:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">187&lt;/span>&lt;span class="cl"> &lt;span class="n">paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">188&lt;/span>&lt;span class="cl"> &lt;span class="n">statuses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">check_all_worktrees&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">paths&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">189&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">statuses&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">190&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; - &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">191&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">status&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">192&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">status&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">193&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">194&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">195&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; (clean)&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">196&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">197&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">198&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 批次命令示範&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">199&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;5. 批次執行多個命令:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">200&lt;/span>&lt;span class="cl"> &lt;span class="n">commands&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">201&lt;/span>&lt;span class="cl"> &lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;user.name&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">202&lt;/span>&lt;span class="cl"> &lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;config&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;user.email&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">203&lt;/span>&lt;span class="cl"> &lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;rev-parse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--short&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;HEAD&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">204&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">205&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">batch_git_commands&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">commands&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">206&lt;/span>&lt;span class="cl"> &lt;span class="n">labels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;使用者名稱&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;使用者信箱&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;當前 commit&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">207&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">label&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">zip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">labels&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">208&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">label&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s1">&amp;#39;(未設定)&amp;#39;&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">209&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">210&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">211&lt;/span>&lt;span class="cl"> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">())&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用範例">使用範例&lt;/h3>
&lt;h4 id="基本使用">基本使用&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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="c1"># 非同步獲取分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_current_branch&lt;/span>&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 class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;當前分支: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 非同步獲取 worktree&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">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_worktree_list&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Worktree 數量: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">())&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="並行操作">並行操作&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_multiple_repos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;同時檢查多個 repo 的狀態&amp;#34;&amp;#34;&amp;#34;&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">tasks&lt;/span> &lt;span class="o">=&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">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">repo&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="k">for&lt;/span> &lt;span class="n">repo&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">repos&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">repo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">zip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">results&lt;/span>&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="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;clean&amp;#34;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;dirty&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">repo&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">status&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10 個 repo，如果每個花 0.5 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="c1"># 並行執行只需要約 0.5 秒！&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="與-fastapi-整合">與 FastAPI 整合&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/git/branch&amp;#34;&lt;/span>&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 class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_branch&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;非同步端點：獲取當前分支&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_current_branch&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">branch&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/git/worktrees&amp;#34;&lt;/span>&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_worktrees&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;非同步端點：獲取 worktree 列表&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;worktrees&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="效能比較">效能比較&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">sync_check_repos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&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="s2">&amp;#34;&amp;#34;&amp;#34;同步版本：依序檢查&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">repo&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">repos&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">repo&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="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_check_repos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&lt;/span>&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="s2">&amp;#34;&amp;#34;&amp;#34;非同步版本：並行檢查&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">repo&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">repo&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">repos&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="c1"># 假設每個 git status 花 0.2 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="n">repos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;/path/to/repo&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="n">sync_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sync_check_repos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># ~2.0 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="n">async_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_check_repos&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">repos&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ~0.2 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;同步: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sync_time&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;非同步: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">async_time&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;加速: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sync_time&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">async_time&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.1f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">x&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計權衡">設計權衡&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>同步 subprocess&lt;/th>
 &lt;th>非同步 subprocess&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>簡單性&lt;/td>
 &lt;td>簡單直覺&lt;/td>
 &lt;td>需要理解 async/await&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>需要 pytest-asyncio&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;h2 id="什麼時候該用非同步-subprocess">什麼時候該用非同步 subprocess？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/git_utils.py</code> 的實際程式碼，展示如何用 asyncio 實現非同步的外部命令執行。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈</a></li>
<li><a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>git_utils.py</code> 使用同步的 <code>subprocess.run</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    執行 git 命令並返回結果
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        args: git 命令參數列表（不含 &#39;git&#39;）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        cwd: 執行目錄
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        timeout: 命令超時時間（秒）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;git&#34;</span><span class="p">]</span> <span class="o">+</span> <span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">capture_output</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">except</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">TimeoutExpired</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">def</span> <span class="nf">get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取當前分支名稱&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="ow">and</span> <span class="n">output</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="k">def</span> <span class="nf">get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取所有 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;worktree&#34;</span><span class="p">,</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="c1"># ... 解析邏輯</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：同步呼叫，容易理解</li>
<li><strong>錯誤處理完善</strong>：處理超時、檔案不存在等情況</li>
<li><strong>API 清晰</strong>：返回 <code>(bool, str)</code> 元組</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<h4 id="問題無法並行執行多個-git-命令">問題：無法並行執行多個 Git 命令</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_all_worktrees</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查所有 worktree 的狀態&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">for</span> <span class="n">worktree</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1"># 每次呼叫都會阻塞等待</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">worktree</span><span class="p">]</span> <span class="o">=</span> <span class="n">status</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 如果有 10 個 worktree，每個花 0.5 秒</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 總共需要 5 秒！</span></span></span></code></pre></div><h4 id="問題阻塞事件迴圈">問題：阻塞事件迴圈</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">handle_request</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 這會阻塞事件迴圈！</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 其他協程無法執行</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span><span class="p">}</span></span></span></code></pre></div><h2 id="進階解決方案非同步-subprocess">進階解決方案：非同步 subprocess</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>非阻塞執行</strong>：不阻塞事件迴圈</li>
<li><strong>並行能力</strong>：可以同時執行多個命令</li>
<li><strong>相容性</strong>：保持與同步版本相似的 API</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立非同步命令執行器">步驟 1：建立非同步命令執行器</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    非同步執行 git 命令
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        args: git 命令參數列表（不含 &#39;git&#39;）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        cwd: 執行目錄
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        timeout: 命令超時時間（秒）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        success, output = await async_run_git_command([&#34;status&#34;])
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># 建立非同步子進程</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="c1"># 等待完成（帶超時）</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="c1"># 處理結果</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2建立便利函式">步驟 2：建立便利函式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步獲取當前分支名稱&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="ow">and</span> <span class="n">output</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_project_root</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步獲取專案根目錄&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;rev-parse&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-toplevel&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步獲取 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;worktree&#34;</span><span class="p">,</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">current_worktree</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">output</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;worktree &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">current_worktree</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;path&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">[</span><span class="mi">9</span><span class="p">:]}</span>  <span class="c1"># len(&#34;worktree &#34;) = 9</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;branch &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">7</span><span class="p">:]</span>  <span class="c1"># len(&#34;branch &#34;) = 7</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">if</span> <span class="n">branch_ref</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;refs/heads/&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">branch_ref</span><span class="p">[</span><span class="mi">11</span><span class="p">:]</span>  <span class="c1"># len(&#34;refs/heads/&#34;) = 11</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">branch_ref</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span> <span class="o">==</span> <span class="s2">&#34;detached&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;detached&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">return</span> <span class="n">worktrees</span></span></span></code></pre></div><h4 id="步驟-3實現並行執行">步驟 3：實現並行執行</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    並行檢查所有 worktree 的狀態
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">worktree</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查單個 worktree&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">worktree</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="n">worktree</span><span class="p">,</span> <span class="n">status</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 並行執行所有檢查</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_all_branches</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    並行獲取所有 worktree 的當前分支
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 分支名} 映射
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">(</span><span class="n">worktree</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">worktree</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="n">worktree</span><span class="p">,</span> <span class="n">branch</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">get_branch</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">非同步 Git 操作工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 asyncio 實現非阻塞的 Git 命令執行。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># ===== 核心功能 =====</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    非同步執行 git 命令
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        args: git 命令參數列表
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">        cwd: 執行目錄
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        timeout: 超時時間（秒）
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        (是否成功, 輸出或錯誤訊息)
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="c1"># ===== 便利函式 =====</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取當前分支&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="ow">and</span> <span class="n">output</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_project_root</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取專案根目錄&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;rev-parse&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-toplevel&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;worktree&#34;</span><span class="p">,</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="n">current_worktree</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">output</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;worktree &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="n">current_worktree</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;path&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">[</span><span class="mi">9</span><span class="p">:]}</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;branch &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">7</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">if</span> <span class="n">branch_ref</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;refs/heads/&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">branch_ref</span><span class="p">[</span><span class="mi">11</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">branch_ref</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span> <span class="o">==</span> <span class="s2">&#34;detached&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;detached&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">return</span> <span class="n">worktrees</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="c1"># ===== 並行操作 =====</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="s2">&#34;&#34;&#34;並行檢查所有 worktree 狀態&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_all_branches</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="s2">&#34;&#34;&#34;並行獲取所有 worktree 的分支&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">branch</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">get_branch</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">batch_git_commands</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="n">commands</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]]]</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    批次執行多個 Git 命令
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">        commands: [(args, cwd), ...] 命令列表
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">        [(success, output), ...] 結果列表
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="n">async_run_git_command</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="k">for</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</span> <span class="ow">in</span> <span class="n">commands</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"># ===== 同步/非同步橋接 =====</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">def</span> <span class="nf">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="s2">    同步版本（相容舊 API）
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="s2">    在已有事件迴圈的環境中，這會建立新的迴圈執行。
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_run_git_command</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="p">,</span> <span class="n">timeout</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="k">def</span> <span class="nf">get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="s2">&#34;&#34;&#34;同步版本：獲取當前分支&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_get_current_branch</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="c1"># ===== 測試 =====</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">
</span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範非同步 Git 操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 非同步 Git 操作示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="c1"># 單一命令</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 獲取當前分支:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   分支: </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="c1"># 獲取專案根目錄</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 獲取專案根目錄:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">root</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_project_root</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   根目錄: </span><span class="si">{</span><span class="n">root</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="c1"># 獲取 worktree 列表</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;3. 獲取 worktree 列表:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="n">branch</span> <span class="o">=</span> <span class="n">wt</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;detached&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">wt</span><span class="p">[</span><span class="s1">&#39;path&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="c1"># 如果有多個 worktree，示範並行操作</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;4. 並行檢查所有 worktree 狀態:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">        <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="k">if</span> <span class="n">status</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">                <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">status</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;       </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;       (clean)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="c1"># 批次命令示範</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;5. 批次執行多個命令:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="n">commands</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="p">([</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;user.name&#34;</span><span class="p">],</span> <span class="kc">None</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="p">([</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;user.email&#34;</span><span class="p">],</span> <span class="kc">None</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="p">([</span><span class="s2">&#34;rev-parse&#34;</span><span class="p">,</span> <span class="s2">&#34;--short&#34;</span><span class="p">,</span> <span class="s2">&#34;HEAD&#34;</span><span class="p">],</span> <span class="kc">None</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">batch_git_commands</span><span class="p">(</span><span class="n">commands</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="n">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;使用者名稱&#34;</span><span class="p">,</span> <span class="s2">&#34;使用者信箱&#34;</span><span class="p">,</span> <span class="s2">&#34;當前 commit&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">    <span class="k">for</span> <span class="n">label</span><span class="p">,</span> <span class="p">(</span><span class="n">success</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">labels</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">label</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s1">&#39;(未設定)&#39;</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">
</span></span><span class="line"><span class="ln">210</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 非同步獲取分支</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;當前分支: </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 非同步獲取 worktree</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Worktree 數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h4 id="並行操作">並行操作</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_multiple_repos</span><span class="p">(</span><span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;同時檢查多個 repo 的狀態&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">repo</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">repo</span> <span class="ow">in</span> <span class="n">repos</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 並行執行，等待全部完成</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">repo</span><span class="p">,</span> <span class="p">(</span><span class="n">success</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">repos</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;clean&#34;</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">output</span> <span class="k">else</span> <span class="s2">&#34;dirty&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">repo</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 10 個 repo，如果每個花 0.5 秒</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 並行執行只需要約 0.5 秒！</span></span></span></code></pre></div><h4 id="與-fastapi-整合">與 FastAPI 整合</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步端點：獲取當前分支&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/worktrees&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_worktrees</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步端點：獲取 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span><span class="p">}</span></span></span></code></pre></div><h2 id="效能比較">效能比較</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">sync_check_repos</span><span class="p">(</span><span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;同步版本：依序檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">for</span> <span class="n">repo</span> <span class="ow">in</span> <span class="n">repos</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">repo</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_check_repos</span><span class="p">(</span><span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非同步版本：並行檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">repo</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">for</span> <span class="n">repo</span> <span class="ow">in</span> <span class="n">repos</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># 假設每個 git status 花 0.2 秒</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">repos</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;/path/to/repo</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">sync_time</span> <span class="o">=</span> <span class="n">sync_check_repos</span><span class="p">(</span><span class="n">repos</span><span class="p">)</span>      <span class="c1"># ~2.0 秒</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">async_time</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_check_repos</span><span class="p">(</span><span class="n">repos</span><span class="p">))</span>  <span class="c1"># ~0.2 秒</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;同步: </span><span class="si">{</span><span class="n">sync_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;非同步: </span><span class="si">{</span><span class="n">async_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速: </span><span class="si">{</span><span class="n">sync_time</span> <span class="o">/</span> <span class="n">async_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>同步 subprocess</th>
          <th>非同步 subprocess</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>簡單性</td>
          <td>簡單直覺</td>
          <td>需要理解 async/await</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>需要 pytest-asyncio</td>
      </tr>
      <tr>
          <td>記憶體</td>
          <td>低</td>
          <td>略高（維護多個進程）</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用非同步-subprocess">什麼時候該用非同步 subprocess？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要同時執行多個外部命令</li>
<li>在非同步框架（FastAPI、aiohttp）中執行</li>
<li>命令執行時間較長，需要同時做其他事</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>只需要執行單一命令</li>
<li>在同步程式碼中（需要額外的橋接）</li>
<li>命令執行非常快（&lt; 10ms）</li>
</ul>
<h2 id="進階使用-taskgrouppython-311">進階：使用 TaskGroup（Python 3.11+）</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_repos_with_taskgroup</span><span class="p">(</span><span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 TaskGroup 管理並行任務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">check_and_store</span><span class="p">(</span><span class="n">repo</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">repo</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">results</span><span class="p">[</span><span class="n">repo</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">for</span> <span class="n">repo</span> <span class="ow">in</span> <span class="n">repos</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">check_and_store</span><span class="p">(</span><span class="n">repo</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p>TaskGroup 的優點：</p>
<ul>
<li>更好的異常處理（一個失敗，全部取消）</li>
<li>結構化並行（明確的範圍）</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>實作 <code>async_is_clean_repo(path)</code> 函式，檢查 repo 是否乾淨</li>
<li>實作 <code>async_get_commit_count(branch)</code> 函式，獲取分支的 commit 數量</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>實作一個 <code>GitRepo</code> 類別，封裝非同步 Git 操作</li>
<li>為非同步版本加入重試機制（失敗時自動重試）</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>實作一個監控工具：每 5 秒並行檢查多個 repo 的狀態，有變化時通知</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-subprocess.html">asyncio.create_subprocess_exec</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.TaskGroup">asyncio.TaskGroup</a></li>
<li><a href="https://github.com/Tinche/aiofiles">aiofiles</a> - 非同步檔案操作</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/case-studies/" data-link-title="案例研究：非同步程式設計實戰" data-link-desc="基於 Hook 系統的 asyncio 實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/01-asyncio/case-studies/parallel-io/" data-link-title="案例：並行 I/O 操作" data-link-desc="用 asyncio.gather 和 TaskGroup 實現高效的並行 I/O 操作">案例：並行 I/O 操作</a></em></p>
]]></content:encoded></item><item><title>案例：宣告式驗證</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Descriptor Protocol 實現宣告式驗證。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 使用命令式驗證方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證模式定義為類別常數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">VALID_NAME_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &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 class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/declarative-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&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;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證單個 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查命名規範&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">valid_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VALID_NAME_PATTERNS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">valid_name&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;warning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;檔案名稱不符合規範: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;建議使用 snake-case 或 kebab-case 命名&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>直覺易懂&lt;/strong>：每個 &lt;code>check_*&lt;/code> 方法負責一項檢查&lt;/li>
&lt;li>&lt;strong>彈性高&lt;/strong>：容易新增或修改檢查邏輯&lt;/li>
&lt;li>&lt;strong>調試方便&lt;/strong>：可以單獨執行任一檢查方法&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要&lt;strong>設定類別&lt;/strong>時（例如 Hook 配置），命令式驗證會有問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfig&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 配置類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">command&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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="c1"># 驗證邏輯散落在 __init__ 中&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&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 class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無效的 Hook 名稱: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無效的事件類型: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">event&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&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="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">command&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;命令不能為空&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：&lt;/p>
&lt;ul>
&lt;li>驗證邏輯在 &lt;code>__init__&lt;/code> 中，不容易重用&lt;/li>
&lt;li>修改屬性時不會重新驗證&lt;/li>
&lt;li>無法在類別定義中看到驗證規則&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案宣告式驗證">進階解決方案：宣告式驗證&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>驗證規則在類別定義中可見&lt;/strong>&lt;/li>
&lt;li>&lt;strong>賦值時自動驗證&lt;/strong>&lt;/li>
&lt;li>&lt;strong>驗證邏輯可重用&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立基礎-descriptor">步驟 1：建立基礎 Descriptor&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidatedField&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證欄位 Descriptor
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> 將驗證邏輯封裝在屬性定義中，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 賦值時自動執行驗證。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;驗證失敗&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> validator: 驗證函式，接受值，返回 bool
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: 驗證失敗時的錯誤訊息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># __set_name__ 會設定這些&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__set_name__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> Python 3.6+ 自動呼叫，取得屬性名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> owner: 擁有此 Descriptor 的類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2"> name: 屬性名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__get__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> 讀取屬性時呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> obj: 實例（如果透過實例存取）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> objtype: 類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> 屬性值，或透過類別存取時返回 Descriptor 本身
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span> &lt;span class="c1"># 透過類別存取，返回 Descriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__set__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">&lt;span class="s2"> 設定屬性時呼叫，執行驗證
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">&lt;span class="s2"> obj: 實例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="s2"> value: 要設定的值
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="s2"> ValueError: 驗證失敗時
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2建立特化的驗證-descriptor">步驟 2：建立特化的驗證 Descriptor&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PatternField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 正則表達式驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 簡化常見的模式匹配驗證。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;格式不符&amp;#34;&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> pattern: 正則表達式模式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: 驗證失敗時的錯誤訊息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">))),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> 選項驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> 限制值必須在指定選項中。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;無效的選項&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> choices: 允許的選項
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: 驗證失敗時的錯誤訊息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">choices&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須是: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> 非空驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> 確保值不為空。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;不能為空&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">&lt;span class="s2"> 範圍驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">&lt;span class="s2"> 確保數值在指定範圍內。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="n">min_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;超出範圍&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">&lt;span class="s2"> min_val: 最小值（None 表示無下限）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl">&lt;span class="s2"> max_val: 最大值（None 表示無上限）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: 驗證失敗時的錯誤訊息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">min_val&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max_val&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">min_val&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">max_val&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">78&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">79&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">81&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">83&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">min_val&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">84&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">85&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">max_val&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">87&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">88&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">89&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39; 且 &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">range_desc&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">90&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-3使用宣告式驗證">步驟 3：使用宣告式驗證&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfig&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> Hook 配置類別 - 宣告式驗證版本
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證規則直接在類別定義中可見。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PatternField&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&amp;#34;&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="s2">&amp;#34;Hook 名稱必須是小寫字母、數字、連字號或底線，且不能以連字號開頭或結尾&amp;#34;&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;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionEnd&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;無效的事件類型&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;命令不能為空&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RangeField&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">min_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">300&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;超時時間無效&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> 初始化 Hook 配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> name: Hook 名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> event: 事件類型
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> command: 執行的命令
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> timeout: 超時時間（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證會在賦值時自動執行。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="c1"># 自動驗證名稱格式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="c1"># 自動驗證事件類型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span> &lt;span class="c1"># 自動驗證非空&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">timeout&lt;/span> &lt;span class="c1"># 自動驗證範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;HookConfig(name=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, event=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;command=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">command&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, timeout=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整程式碼">完整程式碼&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="ch">#!/usr/bin/env python3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">宣告式驗證 - 完整範例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">展示如何用 Descriptor Protocol 實現宣告式驗證。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== Descriptor 定義 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidatedField&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證欄位 Descriptor 基類&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;驗證失敗&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__set_name__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__get__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__set__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PatternField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;正則表達式驗證欄位&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;格式不符&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">))),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;選項驗證欄位&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;無效的選項&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">choices&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須是: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;非空驗證欄位&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;不能為空&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;範圍驗證欄位&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="n">min_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;超出範圍&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">min_val&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max_val&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">min_val&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">max_val&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">min_val&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">max_val&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39; 且 &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">range_desc&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 使用範例 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfig&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 配置類別 - 宣告式驗證&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PatternField&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Hook 名稱格式無效&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionEnd&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;無效的事件類型&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;命令不能為空&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">min_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">300&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;超時時間無效&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">command&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;HookConfig(&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">command&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 測試 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 正確的配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;check-format&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;建立成功: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 修改屬性也會驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;修改 timeout: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證失敗的例子&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl"> &lt;span class="n">bad_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Check-Format&amp;#34;&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">146&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">147&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;驗證失敗: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">153&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;InvalidEvent&amp;#34;&lt;/span> &lt;span class="c1"># 錯誤：無效的事件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;驗證失敗: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">500&lt;/span> &lt;span class="c1"># 錯誤：超出範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;驗證失敗: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用範例">使用範例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 正確的配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&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="o">...&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;check-format&amp;#34;&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="o">...&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&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="o">...&lt;/span> &lt;span class="n">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="o">...&lt;/span> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;check-format&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;PreToolUse&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;python check.py&amp;#39;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 修改屬性也會驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證失敗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Invalid Name&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Hook&lt;/span> &lt;span class="n">名稱格式無效&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;BadEvent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">無效的事件類型&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="n">必須是&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">PreToolUse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PostToolUse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SessionStart&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SessionEnd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">超時時間無效&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="n">必須&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">且&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">300&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計權衡">設計權衡&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>命令式驗證&lt;/th>
 &lt;th>宣告式驗證（Descriptor）&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>Descriptor 可在多個類別重用&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>需要理解 Descriptor Protocol&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>調試&lt;/td>
 &lt;td>容易追蹤&lt;/td>
 &lt;td>需要了解 &lt;code>__get__&lt;/code>/&lt;code>__set__&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>彈性&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>中等（需要遵循 Descriptor 協議）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="什麼時候該用宣告式驗證">什麼時候該用宣告式驗證？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Descriptor Protocol 實現宣告式驗證。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 使用命令式驗證方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 驗證模式定義為類別常數</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/declarative-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">valid_name</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERNS</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">valid_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;建議使用 snake-case 或 kebab-case 命名&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="p">)]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>直覺易懂</strong>：每個 <code>check_*</code> 方法負責一項檢查</li>
<li><strong>彈性高</strong>：容易新增或修改檢查邏輯</li>
<li><strong>調試方便</strong>：可以單獨執行任一檢查方法</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要<strong>設定類別</strong>時（例如 Hook 配置），命令式驗證會有問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 配置類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">command</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="c1"># 驗證邏輯散落在 __init__ 中</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;無效的 Hook 名稱: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">if</span> <span class="n">event</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;無效的事件類型: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">command</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;命令不能為空&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="n">event</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">command</span> <span class="o">=</span> <span class="n">command</span></span></span></code></pre></div><p>問題：</p>
<ul>
<li>驗證邏輯在 <code>__init__</code> 中，不容易重用</li>
<li>修改屬性時不會重新驗證</li>
<li>無法在類別定義中看到驗證規則</li>
</ul>
<h2 id="進階解決方案宣告式驗證">進階解決方案：宣告式驗證</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>驗證規則在類別定義中可見</strong></li>
<li><strong>賦值時自動驗證</strong></li>
<li><strong>驗證邏輯可重用</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立基礎-descriptor">步驟 1：建立基礎 Descriptor</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatedField</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    驗證欄位 Descriptor
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    將驗證邏輯封裝在屬性定義中，
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    賦值時自動執行驗證。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Any</span><span class="p">],</span> <span class="nb">bool</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;驗證失敗&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            validator: 驗證函式，接受值，返回 bool
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># __set_name__ 會設定這些</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        Python 3.6+ 自動呼叫，取得屬性名稱
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">            owner: 擁有此 Descriptor 的類別
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">            name: 屬性名稱
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        讀取屬性時呼叫
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">            obj: 實例（如果透過實例存取）
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">            objtype: 類別
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">            屬性值，或透過類別存取時返回 Descriptor 本身
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span>  <span class="c1"># 透過類別存取，返回 Descriptor</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">        設定屬性時呼叫，執行驗證
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">            obj: 實例
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="s2">            value: 要設定的值
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="s2">            ValueError: 驗證失敗時
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2建立特化的驗證-descriptor">步驟 2：建立特化的驗證 Descriptor</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">PatternField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    正則表達式驗證欄位
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    簡化常見的模式匹配驗證。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;格式不符&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">            pattern: 正則表達式模式
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">v</span><span class="p">))),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    選項驗證欄位
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">    限制值必須在指定選項中。
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;無效的選項&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">            choices: 允許的選項
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須是: </span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">choices</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">class</span> <span class="nc">NonEmptyField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    非空驗證欄位
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    確保值不為空。
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;不能為空&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="n">v</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="k">class</span> <span class="nc">RangeField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">    範圍驗證欄位
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">    確保數值在指定範圍內。
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="n">min_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="n">max_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;超出範圍&#34;</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="s2">            min_val: 最小值（None 表示無下限）
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="s2">            max_val: 最大值（None 表示無上限）
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_val</span> <span class="o">=</span> <span class="n">min_val</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_val</span> <span class="o">=</span> <span class="n">max_val</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl">        <span class="k">def</span> <span class="nf">validator</span><span class="p">(</span><span class="n">v</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">            <span class="k">if</span> <span class="n">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">min_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">            <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">max_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">
</span></span><span class="line"><span class="ln">81</span><span class="cl">        <span class="n">range_desc</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">        <span class="k">if</span> <span class="n">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&gt;= </span><span class="si">{</span><span class="n">min_val</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">        <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&lt;= </span><span class="si">{</span><span class="n">max_val</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">86</span><span class="cl">
</span></span><span class="line"><span class="ln">87</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="n">validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須 </span><span class="si">{</span><span class="s1">&#39; 且 &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">range_desc</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><h4 id="步驟-3使用宣告式驗證">步驟 3：使用宣告式驗證</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Hook 配置類別 - 宣告式驗證版本
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    驗證規則直接在類別定義中可見。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 宣告式驗證：欄位定義即驗證規則</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;Hook 名稱必須是小寫字母、數字、連字號或底線，且不能以連字號開頭或結尾&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionEnd&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;無效的事件類型&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">command</span> <span class="o">=</span> <span class="n">NonEmptyField</span><span class="p">(</span><span class="s2">&#34;命令不能為空&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">timeout</span> <span class="o">=</span> <span class="n">RangeField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">min_val</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">max_val</span><span class="o">=</span><span class="mi">300</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">error_msg</span><span class="o">=</span><span class="s2">&#34;超時時間無效&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">command</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        初始化 Hook 配置
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">            name: Hook 名稱
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            event: 事件類型
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">            command: 執行的命令
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">            timeout: 超時時間（秒）
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">        驗證會在賦值時自動執行。
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>      <span class="c1"># 自動驗證名稱格式</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="n">event</span>    <span class="c1"># 自動驗證事件類型</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">command</span> <span class="o">=</span> <span class="n">command</span>  <span class="c1"># 自動驗證非空</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">timeout</span>  <span class="c1"># 自動驗證範圍</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;HookConfig(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">!r}</span><span class="s2">, event=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">event</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;command=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="si">!r}</span><span class="s2">, timeout=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">宣告式驗證 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 Descriptor Protocol 實現宣告式驗證。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1"># ===== Descriptor 定義 =====</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatedField</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證欄位 Descriptor 基類&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Any</span><span class="p">],</span> <span class="nb">bool</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;驗證失敗&#34;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="k">class</span> <span class="nc">PatternField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;正則表達式驗證欄位&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;格式不符&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">v</span><span class="p">))),</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;選項驗證欄位&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;無效的選項&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須是: </span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">choices</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="k">class</span> <span class="nc">NonEmptyField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="s2">&#34;&#34;&#34;非空驗證欄位&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;不能為空&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="n">v</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="k">class</span> <span class="nc">RangeField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="s2">&#34;&#34;&#34;範圍驗證欄位&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="n">min_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">max_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;超出範圍&#34;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_val</span> <span class="o">=</span> <span class="n">min_val</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_val</span> <span class="o">=</span> <span class="n">max_val</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">def</span> <span class="nf">validator</span><span class="p">(</span><span class="n">v</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">if</span> <span class="n">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">min_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">max_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="n">range_desc</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="k">if</span> <span class="n">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&gt;= </span><span class="si">{</span><span class="n">min_val</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&lt;= </span><span class="si">{</span><span class="n">max_val</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="n">validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須 </span><span class="si">{</span><span class="s1">&#39; 且 &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">range_desc</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="c1"># ===== 使用範例 =====</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 配置類別 - 宣告式驗證&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="s2">&#34;Hook 名稱格式無效&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionEnd&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="s2">&#34;無效的事件類型&#34;</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">command</span> <span class="o">=</span> <span class="n">NonEmptyField</span><span class="p">(</span><span class="s2">&#34;命令不能為空&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="n">timeout</span> <span class="o">=</span> <span class="n">RangeField</span><span class="p">(</span><span class="n">min_val</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_val</span><span class="o">=</span><span class="mi">300</span><span class="p">,</span> <span class="n">error_msg</span><span class="o">=</span><span class="s2">&#34;超時時間無效&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">command</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">30</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="n">event</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">command</span> <span class="o">=</span> <span class="n">command</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">timeout</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;HookConfig(</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">event</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="si">!r}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="c1"># ===== 測試 =====</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="c1"># 正確的配置</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">name</span><span class="o">=</span><span class="s2">&#34;check-format&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;建立成功: </span><span class="si">{</span><span class="n">config</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="c1"># 修改屬性也會驗證</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">60</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;修改 timeout: </span><span class="si">{</span><span class="n">config</span><span class="o">.</span><span class="n">timeout</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="c1"># 驗證失敗的例子</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">bad_config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">            <span class="n">name</span><span class="o">=</span><span class="s2">&#34;Check-Format&#34;</span><span class="p">,</span>  <span class="c1"># 錯誤：大寫</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">            <span class="n">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">            <span class="n">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">config</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="s2">&#34;InvalidEvent&#34;</span>  <span class="c1"># 錯誤：無效的事件</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">500</span>  <span class="c1"># 錯誤：超出範圍</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 正確的配置</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>     <span class="n">name</span><span class="o">=</span><span class="s2">&#34;check-format&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="o">...</span>     <span class="n">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="o">...</span>     <span class="n">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="o">...</span> <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">HookConfig</span><span class="p">(</span><span class="s1">&#39;check-format&#39;</span><span class="p">,</span> <span class="s1">&#39;PreToolUse&#39;</span><span class="p">,</span> <span class="s1">&#39;python check.py&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 修改屬性也會驗證</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">60</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="mi">60</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 驗證失敗</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Invalid Name&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">name</span><span class="p">:</span> <span class="n">Hook</span> <span class="n">名稱格式無效</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="s2">&#34;BadEvent&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">event</span><span class="p">:</span> <span class="n">無效的事件類型</span><span class="err">，</span><span class="n">必須是</span><span class="p">:</span> <span class="n">PreToolUse</span><span class="p">,</span> <span class="n">PostToolUse</span><span class="p">,</span> <span class="n">Stop</span><span class="p">,</span> <span class="n">SessionStart</span><span class="p">,</span> <span class="n">SessionEnd</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">timeout</span><span class="p">:</span> <span class="n">超時時間無效</span><span class="err">，</span><span class="n">必須</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="n">且</span> <span class="o">&lt;=</span> <span class="mi">300</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>命令式驗證</th>
          <th>宣告式驗證（Descriptor）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>可讀性</td>
          <td>驗證邏輯散落在方法中</td>
          <td>驗證規則在類別定義中可見</td>
      </tr>
      <tr>
          <td>重用性</td>
          <td>需要複製驗證邏輯</td>
          <td>Descriptor 可在多個類別重用</td>
      </tr>
      <tr>
          <td>賦值驗證</td>
          <td>需要手動呼叫驗證</td>
          <td>自動在賦值時驗證</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>簡單直覺</td>
          <td>需要理解 Descriptor Protocol</td>
      </tr>
      <tr>
          <td>調試</td>
          <td>容易追蹤</td>
          <td>需要了解 <code>__get__</code>/<code>__set__</code></td>
      </tr>
      <tr>
          <td>彈性</td>
          <td>高</td>
          <td>中等（需要遵循 Descriptor 協議）</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用宣告式驗證">什麼時候該用宣告式驗證？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>資料類別（Data Class）需要驗證</li>
<li>同樣的驗證規則需要在多處使用</li>
<li>希望在類別定義中清楚看到驗證規則</li>
<li>需要在屬性賦值時自動驗證</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的一次性驗證</li>
<li>驗證邏輯需要存取多個欄位</li>
<li>團隊不熟悉 Descriptor Protocol</li>
<li>驗證邏輯經常變動</li>
</ul>
<h2 id="進階與-dataclass-結合">進階：與 dataclass 結合</h2>
<p>Python 3.7+ 的 <code>dataclass</code> 可以與 Descriptor 結合：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigDataclass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 dataclass + Descriptor&#34;&#34;&#34;</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"># Descriptor 欄位需要特殊處理</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="nb">repr</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">_event</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="nb">repr</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># 公開欄位</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">event</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Descriptor 定義（類別變數）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span> <span class="s2">&#34;無效的名稱&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">((</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">),</span> <span class="s2">&#34;無效的事件&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># 觸發 Descriptor 驗證</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">event</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>實作一個 <code>EmailField</code> Descriptor，驗證 email 格式</li>
<li>實作一個 <code>LengthField</code> Descriptor，驗證字串長度</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>修改 <code>ValidatedField</code>，支援可選欄位（允許 <code>None</code>）</li>
<li>實作一個 <code>CompositeField</code>，可以組合多個驗證規則</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>參考 <code>hook_validator.py</code> 的 <code>check_lib_imports</code> 方法，用 Descriptor 實現「根據欄位值決定是否需要驗證另一個欄位」的邏輯</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/howto/descriptor.html">Python Descriptor HOWTO</a></li>
<li><a href="https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/">Django Model Field</a></li>
<li><a href="https://docs.pydantic.dev/latest/concepts/validators/">Pydantic Field Validators</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/" data-link-title="案例研究：元編程實戰" data-link-desc="基於 Hook 系統的元編程實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">案例：自動註冊機制</a></em></p>
]]></content:encoded></item><item><title>案例：效能分析實戰</title><link>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/profiling/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/profiling/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code> 的實際程式碼，展示如何用 cProfile 和 line_profiler 進行效能分析。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四基礎章節&lt;/a>&lt;/li>
&lt;li>Python 正則表達式基礎&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 是一個 Markdown 連結檢查工具，核心功能是解析文件中的連結並驗證其有效性。以下是關鍵的解析方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MarkdownLinkChecker&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="s2">&amp;#34;&amp;#34;&amp;#34;Markdown link checker with precompiled regex patterns&amp;#34;&amp;#34;&amp;#34;&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"># Precompiled regex patterns at class level (good practice!)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">INLINE_LINK_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&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="sa">r&lt;/span>&lt;span class="s1">&amp;#39;(?&amp;lt;!!)\[([^\]]+)\]\(([^)]+)\)&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;^\s*\[([^\]]+)\]:\s*(.+)$&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">MULTILINE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">REFERENCE_USE_PATTERN&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\[([^\]]+)\]\[([^\]]+)\]&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> Parse all links in Markdown content
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> content: Markdown content string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[dict]: List of links with text, target, line
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="c1"># First, collect reference-style link definitions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_DEF_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_target&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ref_target&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Track code block state&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Parse inline links line by line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">line_num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Check for code block boundaries&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;```&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="n">in_code_block&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">in_code_block&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Skip links inside code blocks&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">in_code_block&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Inline links: [text](/python-advanced/04-cpython-internals/case-studies/profiling/target)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INLINE_LINK_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Reference-style links: [text][ref]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="k">match&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">REFERENCE_USE_PATTERN&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">finditer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="n">ref_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">ref_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">reference_defs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ref_name&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line_num&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">links&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="效能問題">效能問題&lt;/h3>
&lt;p>處理大型文件時可能出現：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>正則表達式效率問題&lt;/strong>：複雜的 pattern 可能導致回溯&lt;/li>
&lt;li>&lt;strong>重複編譯正則表達式&lt;/strong>：若 pattern 在方法內定義，每次呼叫都會重新編譯&lt;/li>
&lt;li>&lt;strong>不必要的字串操作&lt;/strong>：&lt;code>split()&lt;/code> 會建立新的字串列表&lt;/li>
&lt;li>&lt;strong>多次遍歷&lt;/strong>：分別處理引用定義和行內連結&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="分析目標">分析目標&lt;/h3>
&lt;ol>
&lt;li>找出效能瓶頸所在&lt;/li>
&lt;li>量化各部分的時間消耗&lt;/li>
&lt;li>驗證優化效果&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1使用-cprofile-進行函式級分析">步驟 1：使用 cProfile 進行函式級分析&lt;/h4>
&lt;p>cProfile 是 Python 標準庫的效能分析工具，可以測量每個函式的呼叫次數和執行時間。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">cProfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pstats&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StringIO&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">profile_link_checker&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Profile the markdown link checker with cProfile&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Create test content with many links&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">test_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_test_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1000&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">checker&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MarkdownLinkChecker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Create profiler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cProfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Profile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Run profiling&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">):&lt;/span> &lt;span class="c1"># Run multiple times for better statistics&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">checker&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Analyze results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StringIO&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pstats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">profiler&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort_stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;cumulative&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Sort by cumulative time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print_stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Show top 20 functions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getvalue&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">stats&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_test_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_links&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Generate test Markdown content with specified number of links&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;# Test Document&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_links&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Inline link&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Check out [Link &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">](https://example.com/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Reference-style link&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;See [Reference &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">][ref&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">]&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Plain text with potential regex traps&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;This is paragraph &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> with some [text] that looks like links.&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Add reference definitions at the end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_links&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[ref&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">]: https://example.com/ref/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="n">profile_link_checker&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>執行方式：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code> 的實際程式碼，展示如何用 cProfile 和 line_profiler 進行效能分析。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四基礎章節</a></li>
<li>Python 正則表達式基礎</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 是一個 Markdown 連結檢查工具，核心功能是解析文件中的連結並驗證其有效性。以下是關鍵的解析方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Markdown link checker with precompiled regex patterns&#34;&#34;&#34;</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"># Precompiled regex patterns at class level (good practice!)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        Parse all links in Markdown content
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            content: Markdown content string
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">            list[dict]: List of links with text, target, line
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="c1"># First, collect reference-style link definitions</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="c1"># Track code block state</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="c1"># Parse inline links line by line</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="c1"># Check for code block boundaries</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="c1"># Skip links inside code blocks</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="c1"># Inline links: [text](/python-advanced/04-cpython-internals/case-studies/profiling/target)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="c1"># Reference-style links: [text][ref]</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">        <span class="k">return</span> <span class="n">links</span></span></span></code></pre></div><h3 id="效能問題">效能問題</h3>
<p>處理大型文件時可能出現：</p>
<ul>
<li><strong>正則表達式效率問題</strong>：複雜的 pattern 可能導致回溯</li>
<li><strong>重複編譯正則表達式</strong>：若 pattern 在方法內定義，每次呼叫都會重新編譯</li>
<li><strong>不必要的字串操作</strong>：<code>split()</code> 會建立新的字串列表</li>
<li><strong>多次遍歷</strong>：分別處理引用定義和行內連結</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="分析目標">分析目標</h3>
<ol>
<li>找出效能瓶頸所在</li>
<li>量化各部分的時間消耗</li>
<li>驗證優化效果</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1使用-cprofile-進行函式級分析">步驟 1：使用 cProfile 進行函式級分析</h4>
<p>cProfile 是 Python 標準庫的效能分析工具，可以測量每個函式的呼叫次數和執行時間。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">profile_link_checker</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Profile the markdown link checker with cProfile&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># Create test content with many links</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">test_content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="n">num_links</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># Create profiler</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># Run profiling</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>  <span class="c1"># Run multiple times for better statistics</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># Analyze results</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">stream</span> <span class="o">=</span> <span class="n">StringIO</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">,</span> <span class="n">stream</span><span class="o">=</span><span class="n">stream</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="s1">&#39;cumulative&#39;</span><span class="p">)</span>  <span class="c1"># Sort by cumulative time</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>  <span class="c1"># Show top 20 functions</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">stream</span><span class="o">.</span><span class="n">getvalue</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">stats</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">generate_test_content</span><span class="p">(</span><span class="n">num_links</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate test Markdown content with specified number of links&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;# Test Document</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="c1"># Inline link</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check out [Link </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](https://example.com/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="c1"># Reference-style link</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;See [Reference </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">][ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="c1"># Plain text with potential regex traps</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;This is paragraph </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2"> with some [text] that looks like links.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># Add reference definitions at the end</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]: https://example.com/ref/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="n">profile_link_checker</span><span class="p">()</span></span></span></code></pre></div><p>執行方式：</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"><span class="c1"># Direct execution</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">python profile_link_checker.py
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Using cProfile from command line</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">python -m cProfile -s cumulative markdown_link_checker.py --dir ./docs/</span></span></code></pre></div><h4 id="步驟-2使用-pstats-分析結果">步驟 2：使用 pstats 分析結果</h4>
<p>pstats 模組提供更細緻的結果分析功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pstats</span> <span class="kn">import</span> <span class="n">SortKey</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">detailed_analysis</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Perform detailed analysis with pstats&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Profile the code</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># Run the target function</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">50</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="c1"># Create Stats object</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># Different sorting options</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Top 10 by CUMULATIVE time (including sub-calls)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CUMULATIVE</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Top 10 by TOTAL time (excluding sub-calls)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">TIME</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Top 10 by CALL count&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CALLS</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="c1"># Filter by function name</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Functions containing &#39;parse&#39; or &#39;match&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">TIME</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="s1">&#39;parse|match&#39;</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="c1"># Show callers of a specific function</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Who calls &#39;finditer&#39;?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">print_callers</span><span class="p">(</span><span class="s1">&#39;finditer&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="c1"># Save stats to file for later analysis</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">dump_stats</span><span class="p">(</span><span class="s1">&#39;link_checker.prof&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">return</span> <span class="n">stats</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">def</span> <span class="nf">load_and_compare_profiles</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load and compare saved profile data&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="c1"># Load saved profile</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="s1">&#39;link_checker.prof&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="c1"># Add another profile for comparison</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="n">stats</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s1">&#39;link_checker_optimized.prof&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="c1"># Print combined stats</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">        <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CUMULATIVE</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Profile files not found. Run detailed_analysis() first.&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-3使用-line_profiler-進行行級分析">步驟 3：使用 line_profiler 進行行級分析</h4>
<p>line_profiler 可以分析每一行程式碼的執行時間，適合精確定位瓶頸。</p>
<p>首先安裝 line_profiler：</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">pip install line_profiler</span></span></code></pre></div><p>使用裝飾器標記要分析的函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1"># profile_lines.py</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">line_profiler</span> <span class="kn">import</span> <span class="n">profile</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="nd">@profile</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="k">def</span> <span class="nf">parse_markdown_links_profiled</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    Line-by-line profiled version of parse_markdown_links
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="c1"># Collect reference definitions</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">        <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="c1"># These regex operations might be slow</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">                <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">                <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">                <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="c1"># Alternative: Manual timing for specific sections</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="k">def</span> <span class="nf">parse_with_timing</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Manual timing for detailed analysis&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="n">timings</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="c1"># Time: split lines</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">timings</span><span class="p">[</span><span class="s1">&#39;split_lines&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="c1"># Time: parse reference definitions</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">timings</span><span class="p">[</span><span class="s1">&#39;parse_refs&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="c1"># Time: main parsing loop</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="n">timings</span><span class="p">[</span><span class="s1">&#39;main_loop&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="k">return</span> <span class="n">links</span><span class="p">,</span> <span class="n">timings</span></span></span></code></pre></div><p>執行 line_profiler：</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"><span class="c1"># Run with kernprof</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">kernprof -l -v profile_lines.py
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Or use the newer approach</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">python -m line_profiler profile_lines.py</span></span></code></pre></div><h4 id="步驟-4分析正則表達式效能">步驟 4：分析正則表達式效能</h4>
<p>正則表達式是常見的效能瓶頸，需要特別分析：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_regex_patterns</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Compare different regex pattern implementations&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="c1"># Test content with various edge cases</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">test_lines</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">        <span class="s2">&#34;Check out [Link](https://example.com)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">        <span class="s2">&#34;See [Text with [brackets]](/python-advanced/04-cpython-internals/case-studies/profiling/url)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="s2">&#34;Multiple [link1](/python-advanced/04-cpython-internals/case-studies/profiling/url1) and [link2](/python-advanced/04-cpython-internals/case-studies/profiling/url2)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="s2">&#34;No links here, just plain text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="s2">&#34;Tricky [text](/python-advanced/04-cpython-internals/case-studies/profiling/url) with more [text](/python-advanced/04-cpython-internals/case-studies/profiling/url) links&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">        <span class="s2">&#34;![Image](/python-advanced/04-cpython-internals/case-studies/profiling/image.png) should be ignored&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="s2">&#34;[Link with spaces]( url with spaces )&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="p">]</span> <span class="o">*</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">test_content</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">test_lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="c1"># Pattern 1: Original pattern</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">pattern1</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="c1"># Pattern 2: Possessive-like (using atomic group simulation)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="c1"># Note: Python re doesn&#39;t support possessive quantifiers directly</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">pattern2</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]*)\]\(([^)]*)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="c1"># Pattern 3: More specific pattern</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">pattern3</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\[\]]+)\]\(([^()]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="c1"># Pattern 4: Non-capturing groups where possible</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">pattern4</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">patterns</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="s2">&#34;original&#34;</span><span class="p">:</span> <span class="n">pattern1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="s2">&#34;non_greedy&#34;</span><span class="p">:</span> <span class="n">pattern2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="s2">&#34;more_specific&#34;</span><span class="p">:</span> <span class="n">pattern3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="s2">&#34;optimized&#34;</span><span class="p">:</span> <span class="n">pattern4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="c1"># Warmup</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">test_content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="n">matches</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">test_content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="s2">&#34;time&#34;</span><span class="p">:</span> <span class="n">elapsed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">            <span class="s2">&#34;matches&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">matches</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="c1"># Print comparison</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Regex Pattern Performance Comparison&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Pattern&#39;</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Time (s)&#39;</span><span class="si">:</span><span class="s2">&lt;12</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Matches&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="n">baseline</span> <span class="o">=</span> <span class="n">results</span><span class="p">[</span><span class="s2">&#34;original&#34;</span><span class="p">][</span><span class="s2">&#34;time&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">baseline</span> <span class="o">/</span> <span class="n">data</span><span class="p">[</span><span class="s2">&#34;time&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">data</span><span class="p">[</span><span class="s1">&#39;time&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;12.4f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">data</span><span class="p">[</span><span class="s1">&#39;matches&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="k">def</span> <span class="nf">analyze_regex_backtracking</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Analyze potential regex backtracking issues&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="c1"># Patterns that might cause backtracking</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="n">problematic_patterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[(.+)\]\((.+)\)&#39;</span><span class="p">,</span> <span class="s2">&#34;Greedy .+ can backtrack&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">,</span> <span class="s2">&#34;Negated class - better&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[(.*?)\]\((.*?)\)&#39;</span><span class="p">,</span> <span class="s2">&#34;Non-greedy - still may backtrack&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="c1"># Pathological input that triggers backtracking</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="n">pathological</span> <span class="o">=</span> <span class="s2">&#34;[&#34;</span> <span class="o">+</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">100</span> <span class="o">+</span> <span class="s2">&#34;]&#34;</span>  <span class="c1"># No closing bracket pattern</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Regex Backtracking Analysis&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="k">for</span> <span class="n">pattern_str</span><span class="p">,</span> <span class="n">description</span> <span class="ow">in</span> <span class="n">problematic_patterns</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="c1"># Set a timeout using signal (Unix only)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pathological</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Pattern: </span><span class="si">{</span><span class="n">pattern_str</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Description: </span><span class="si">{</span><span class="n">description</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Time: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Match: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Pattern: </span><span class="si">{</span><span class="n">pattern_str</span><span class="si">}</span><span class="s2"> - Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="k">def</span> <span class="nf">compare_compile_vs_inline</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Compare precompiled vs inline regex performance&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">test_content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="c1"># Test 1: Precompiled pattern (recommended)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="n">compiled_pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">compiled_pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">test_content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="c1"># Test 2: Inline pattern (compiled each time by re module cache)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">,</span> <span class="n">test_content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="n">inline_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="c1"># Test 3: Pattern compiled inside loop (worst case)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">test_content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="n">loop_compile_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Compile Strategy Comparison&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Strategy&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Time (s)&#39;</span><span class="si">:</span><span class="s2">&lt;12</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Relative&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Precompiled (class)&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">&lt;12.4f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;1.00x&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Inline (re cache)&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">inline_time</span><span class="si">:</span><span class="s2">&lt;12.4f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">inline_time</span><span class="o">/</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Compile in loop&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">loop_compile_time</span><span class="si">:</span><span class="s2">&lt;12.4f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">loop_compile_time</span><span class="o">/</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-5優化建議與驗證">步驟 5：優化建議與驗證</h4>
<p>根據分析結果，實作優化版本並驗證效果：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">slots</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>  <span class="c1"># Python 3.10+ optimization</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">LinkInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Link information with memory-efficient storage&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">text</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="k">class</span> <span class="nc">OptimizedLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Optimized Markdown link checker based on profiling results&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="c1"># Precompiled patterns at class level</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">CODE_BLOCK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^```&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links_optimized</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkInfo</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        Optimized link parsing with reduced memory allocation
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        Optimizations:
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">        1. Use dataclass with slots for link storage
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">        2. Single pass where possible
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        3. Avoid repeated string operations
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">        4. Use local variables for frequently accessed attributes
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="c1"># Cache pattern references for faster access</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="n">inline_pattern</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="n">ref_use_pattern</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="n">ref_def_pattern</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="c1"># Collect reference definitions first (single pass over content)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">            <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">ref_def_pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="c1"># Process line by line</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">line_start</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line_end</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_find_line_positions</span><span class="p">(</span><span class="n">content</span><span class="p">),</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="n">line</span> <span class="o">=</span> <span class="n">content</span><span class="p">[</span><span class="n">line_start</span><span class="p">:</span><span class="n">line_end</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="c1"># Fast code block check</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">lstrip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="n">line_start</span> <span class="o">=</span> <span class="n">line_end</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                <span class="c1"># Parse inline links</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">inline_pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                        <span class="n">text</span><span class="o">=</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                        <span class="n">target</span><span class="o">=</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                    <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                <span class="c1"># Parse reference links</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">ref_use_pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                    <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                    <span class="n">target</span> <span class="o">=</span> <span class="n">reference_defs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">ref_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                    <span class="k">if</span> <span class="n">target</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                        <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LinkInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                            <span class="n">text</span><span class="o">=</span><span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                            <span class="n">target</span><span class="o">=</span><span class="n">target</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">                            <span class="n">line</span><span class="o">=</span><span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                        <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">line_start</span> <span class="o">=</span> <span class="n">line_end</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">def</span> <span class="nf">_find_line_positions</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Generator that yields line end positions&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">newline</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">,</span> <span class="n">pos</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">if</span> <span class="n">newline</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="k">yield</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">yield</span> <span class="n">newline</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="n">newline</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="k">def</span> <span class="nf">verify_optimization</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Verify that optimizations maintain correctness and improve performance&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="c1"># Generate test content</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="n">test_content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="c1"># Original implementation</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="n">original</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="c1"># Optimized implementation</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">    <span class="n">optimized</span> <span class="o">=</span> <span class="n">OptimizedLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="c1"># Verify correctness</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">original_links</span> <span class="o">=</span> <span class="n">original</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="n">optimized_links</span> <span class="o">=</span> <span class="n">optimized</span><span class="o">.</span><span class="n">parse_markdown_links_optimized</span><span class="p">(</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="c1"># Compare results</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">original_links</span><span class="p">)</span> <span class="o">==</span> <span class="nb">len</span><span class="p">(</span><span class="n">optimized_links</span><span class="p">),</span> \
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;Link count mismatch: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">original_links</span><span class="p">)</span><span class="si">}</span><span class="s2"> vs </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">optimized_links</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">for</span> <span class="n">orig</span><span class="p">,</span> <span class="n">opt</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">original_links</span><span class="p">,</span> <span class="n">optimized_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">assert</span> <span class="n">orig</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="n">opt</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Text mismatch: </span><span class="si">{</span><span class="n">orig</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> vs </span><span class="si">{</span><span class="n">opt</span><span class="o">.</span><span class="n">text</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">assert</span> <span class="n">orig</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="n">opt</span><span class="o">.</span><span class="n">target</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Target mismatch&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">assert</span> <span class="n">orig</span><span class="p">[</span><span class="s2">&#34;line&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="n">opt</span><span class="o">.</span><span class="n">line</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Line mismatch&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Correctness verified!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="c1"># Original</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="n">original</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="n">original_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="c1"># Optimized</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">optimized</span><span class="o">.</span><span class="n">parse_markdown_links_optimized</span><span class="p">(</span><span class="n">test_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="n">optimized_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Performance Comparison (</span><span class="si">{</span><span class="n">iterations</span><span class="si">}</span><span class="s2"> iterations)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Original:  </span><span class="si">{</span><span class="n">original_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Optimized: </span><span class="si">{</span><span class="n">optimized_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Speedup:   </span><span class="si">{</span><span class="n">original_time</span> <span class="o">/</span> <span class="n">optimized_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<p>以下是整合所有分析功能的完整腳本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Performance Profiling Script for Markdown Link Checker
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">This script demonstrates various profiling techniques:
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">1. cProfile for function-level analysis
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">2. pstats for detailed statistics
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">3. line_profiler for line-by-line analysis
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">4. Regex pattern benchmarking
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">5. Optimization verification
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">Usage:
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    python profiling_demo.py [--full|--quick|--regex|--verify]
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">import</span> <span class="nn">argparse</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="kn">from</span> <span class="nn">pstats</span> <span class="kn">import</span> <span class="n">SortKey</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="c1"># ===== Original Implementation =====</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Original Markdown link checker for comparison&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">REFERENCE_DEF_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;^\s*\[([^\]]+)\]:\s*(.+)$&#39;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">MULTILINE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="n">REFERENCE_USE_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\[([^\]]+)\]\[([^\]]+)\]&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_markdown_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="n">reference_defs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_DEF_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">            <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="n">ref_target</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref_target</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">REFERENCE_USE_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">ref_name</span> <span class="o">=</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="k">if</span> <span class="n">ref_name</span> <span class="ow">in</span> <span class="n">reference_defs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                    <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                        <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                        <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="n">reference_defs</span><span class="p">[</span><span class="n">ref_name</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                        <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                    <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="c1"># ===== Test Data Generation =====</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">
</span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="k">def</span> <span class="nf">generate_test_content</span><span class="p">(</span><span class="n">num_links</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Generate test Markdown content&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;# Test Document</span><span class="se">\n\n</span><span class="s2">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check out [Link </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">](https://example.com/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">)</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="k">elif</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;See [Reference </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">][ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Paragraph </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2"> with some text.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_links</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[ref</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">]: https://example.com/ref/</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="c1"># ===== Profiling Functions =====</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="k">def</span> <span class="nf">run_cprofile_analysis</span><span class="p">(</span><span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">50</span><span class="p">,</span> <span class="n">num_links</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">500</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Run cProfile analysis&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Running cProfile Analysis&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="n">num_links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CUMULATIVE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Top 15 functions by cumulative time:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">15</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="k">return</span> <span class="n">stats</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="k">def</span> <span class="nf">run_regex_benchmark</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Benchmark different regex patterns&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Regex Pattern Benchmark&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="n">patterns</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="s2">&#34;Original&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="s2">&#34;Non-greedy inner&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]*?)\]\(([^)]*?)\)&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="s2">&#34;Anchored&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\[\]]+)\]\(([^()]+)\)&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Pattern&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Time (ms)&#39;</span><span class="si">:</span><span class="s2">&lt;12</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Matches&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="c1"># Warmup</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">            <span class="n">matches</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">&lt;12.2f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">matches</span><span class="p">)</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">
</span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="k">def</span> <span class="nf">run_compile_comparison</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Compare precompiled vs inline regex&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Compile Strategy Comparison&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="n">pattern_str</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">
</span></span><span class="line"><span class="ln">160</span><span class="cl">    <span class="c1"># Precompiled</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="n">compiled</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">compiled</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="c1"># Inline (uses re module cache)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">,</span> <span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">inline_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Strategy&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Time (ms)&#39;</span><span class="si">:</span><span class="s2">&lt;12</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Relative&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Precompiled&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">compiled_time</span><span class="o">*</span><span class="mi">1000</span><span class="si">:</span><span class="s2">&lt;12.2f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;1.00x&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Inline (cached)&#39;</span><span class="si">:</span><span class="s2">&lt;25</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">inline_time</span><span class="o">*</span><span class="mi">1000</span><span class="si">:</span><span class="s2">&lt;12.2f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">inline_time</span><span class="o">/</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Profiling Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;--full&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;Run full analysis&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;--quick&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;Run quick analysis&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;--regex&#39;</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">&#39;store_true&#39;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s1">&#39;Run regex benchmark&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl">    <span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">regex</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="n">run_regex_benchmark</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="n">run_compile_comparison</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">    <span class="k">elif</span> <span class="n">args</span><span class="o">.</span><span class="n">quick</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">        <span class="n">run_cprofile_analysis</span><span class="p">(</span><span class="n">iterations</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">num_links</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="n">run_cprofile_analysis</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">        <span class="n">run_regex_benchmark</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="n">run_compile_comparison</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h3 id="分析結果範例">分析結果範例</h3>
<p>執行 cProfile 分析後的典型輸出：</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">Running cProfile Analysis
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">======================================================================
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Top 15 functions by cumulative time:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">----------------------------------------------------------------------
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">         125003 function calls in 0.892 seconds
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   Ordered by: cumulative time
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
</span></span><span class="line"><span class="ln">11</span><span class="cl">       50    0.012    0.000    0.892    0.018 checker.py:45(parse_markdown_links)
</span></span><span class="line"><span class="ln">12</span><span class="cl">    25000    0.234    0.000    0.567    0.000 {method &#39;finditer&#39; of &#39;re.Pattern&#39;}
</span></span><span class="line"><span class="ln">13</span><span class="cl">       50    0.089    0.002    0.089    0.002 {method &#39;split&#39; of &#39;str&#39; objects}
</span></span><span class="line"><span class="ln">14</span><span class="cl">    50000    0.156    0.000    0.156    0.000 {method &#39;append&#39; of &#39;list&#39; objects}
</span></span><span class="line"><span class="ln">15</span><span class="cl">    25000    0.098    0.000    0.098    0.000 {method &#39;group&#39; of &#39;re.Match&#39;}
</span></span><span class="line"><span class="ln">16</span><span class="cl">    12500    0.045    0.000    0.045    0.000 {method &#39;lower&#39; of &#39;str&#39; objects}
</span></span><span class="line"><span class="ln">17</span><span class="cl">    12500    0.034    0.000    0.034    0.000 {method &#39;strip&#39; of &#39;str&#39; objects}
</span></span><span class="line"><span class="ln">18</span><span class="cl">    25000    0.067    0.000    0.067    0.000 {method &#39;startswith&#39; of &#39;str&#39; objects}
</span></span><span class="line"><span class="ln">19</span><span class="cl">       50    0.023    0.000    0.023    0.000 {built-in method builtins.enumerate}</span></span></code></pre></div><p>從上述結果可以觀察到：</p>
<ol>
<li><strong><code>finditer</code> 佔用最多時間</strong>：正則表達式匹配是主要瓶頸</li>
<li><strong><code>split</code> 操作耗時明顯</strong>：每次解析都建立新的行列表</li>
<li><strong><code>append</code> 呼叫頻繁</strong>：大量的字典建立和列表操作</li>
</ol>
<p>line_profiler 的典型輸出：</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">Timer unit: 1e-06 s
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Total time: 0.456 s
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">File: checker.py
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Function: parse_markdown_links at line 45
</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">Line #      Hits         Time  Per Hit   % Time  Line Contents
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">==============================================================
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    45                                           def parse_markdown_links(self, content):
</span></span><span class="line"><span class="ln">10</span><span class="cl">    46        50       1234.0     24.7      0.3      links = []
</span></span><span class="line"><span class="ln">11</span><span class="cl">    47        50      89012.0   1780.2     19.5      lines = content.split(&#39;\n&#39;)
</span></span><span class="line"><span class="ln">12</span><span class="cl">    48
</span></span><span class="line"><span class="ln">13</span><span class="cl">    49        50         56.0      1.1      0.0      reference_defs = {}
</span></span><span class="line"><span class="ln">14</span><span class="cl">    50      2500      45678.0     18.3     10.0      for match in self.REFERENCE_DEF_PATTERN.finditer(content):
</span></span><span class="line"><span class="ln">15</span><span class="cl">    51      2450       3456.0      1.4      0.8          ref_name = match.group(1).lower()
</span></span><span class="line"><span class="ln">16</span><span class="cl">    52      2450       2345.0      1.0      0.5          ref_target = match.group(2).strip()
</span></span><span class="line"><span class="ln">17</span><span class="cl">    53      2450       1234.0      0.5      0.3          reference_defs[ref_name] = ref_target
</span></span><span class="line"><span class="ln">18</span><span class="cl">    54
</span></span><span class="line"><span class="ln">19</span><span class="cl">    55        50         34.0      0.7      0.0      in_code_block = False
</span></span><span class="line"><span class="ln">20</span><span class="cl">    56
</span></span><span class="line"><span class="ln">21</span><span class="cl">    57     25050      12345.0      0.5      2.7      for line_num, line in enumerate(lines, start=1):
</span></span><span class="line"><span class="ln">22</span><span class="cl">    58     25000      34567.0      1.4      7.6          if line.strip().startswith(&#34;```&#34;):
</span></span><span class="line"><span class="ln">23</span><span class="cl">    59                                                       in_code_block = not in_code_block
</span></span><span class="line"><span class="ln">24</span><span class="cl">    60                                                       continue
</span></span><span class="line"><span class="ln">25</span><span class="cl">    61
</span></span><span class="line"><span class="ln">26</span><span class="cl">    62     25000       5678.0      0.2      1.2          if in_code_block:
</span></span><span class="line"><span class="ln">27</span><span class="cl">    63                                                       continue
</span></span><span class="line"><span class="ln">28</span><span class="cl">    64
</span></span><span class="line"><span class="ln">29</span><span class="cl">    65     25000     156789.0      6.3     34.4          for match in self.INLINE_LINK_PATTERN.finditer(line):
</span></span><span class="line"><span class="ln">30</span><span class="cl">    66      8350      23456.0      2.8      5.1              links.append({...})
</span></span><span class="line"><span class="ln">31</span><span class="cl">    67
</span></span><span class="line"><span class="ln">32</span><span class="cl">    68     25000      78901.0      3.2     17.3          for match in self.REFERENCE_USE_PATTERN.finditer(line):
</span></span><span class="line"><span class="ln">33</span><span class="cl">    69      2450       1234.0      0.5      0.3              ref_name = match.group(2).lower()
</span></span><span class="line"><span class="ln">34</span><span class="cl">    70      2450        567.0      0.2      0.1              if ref_name in reference_defs:
</span></span><span class="line"><span class="ln">35</span><span class="cl">    71      2450       1234.0      0.5      0.3                  links.append({...})
</span></span><span class="line"><span class="ln">36</span><span class="cl">    72
</span></span><span class="line"><span class="ln">37</span><span class="cl">    73        50         23.0      0.5      0.0      return links</span></span></code></pre></div><p>關鍵發現：</p>
<ul>
<li><strong>第 65 行（行內連結匹配）佔 34.4%</strong>：這是最大的瓶頸</li>
<li><strong>第 47 行（split）佔 19.5%</strong>：字串分割是第二大消耗</li>
<li><strong>第 68 行（引用連結匹配）佔 17.3%</strong>：也是重要的優化目標</li>
</ul>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>不分析</th>
          <th>使用 cProfile</th>
          <th>使用 line_profiler</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發時間</td>
          <td>少</td>
          <td>中等</td>
          <td>較多</td>
      </tr>
      <tr>
          <td>分析粒度</td>
          <td>無</td>
          <td>函式級</td>
          <td>行級</td>
      </tr>
      <tr>
          <td>效能開銷</td>
          <td>無</td>
          <td>低</td>
          <td>較高</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>簡單程式</td>
          <td>一般優化</td>
          <td>精確定位</td>
      </tr>
      <tr>
          <td>學習成本</td>
          <td>無</td>
          <td>低</td>
          <td>中等</td>
      </tr>
      <tr>
          <td>結果準確度</td>
          <td>-</td>
          <td>高</td>
          <td>非常高</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該做效能分析">什麼時候該做效能分析？</h2>
<p>適合分析：</p>
<ul>
<li>程式明顯變慢</li>
<li>處理大量資料</li>
<li>發布前的效能驗證</li>
<li>正則表達式複雜時</li>
<li>有迴圈處理大量項目</li>
</ul>
<p>不建議過早優化：</p>
<ul>
<li>功能還在開發中</li>
<li>使用頻率很低</li>
<li>效能已經足夠</li>
<li>可讀性更重要時</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習用-cprofile-分析排序函式">基礎練習：用 cProfile 分析排序函式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">Exercise 1: Profile different sorting approaches
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">pstats</span> <span class="kn">import</span> <span class="n">SortKey</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">bubble_sort</span><span class="p">(</span><span class="n">arr</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Bubble sort implementation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">n</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">arr</span> <span class="o">=</span> <span class="n">arr</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">n</span> <span class="o">-</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">if</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                <span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">],</span> <span class="n">arr</span><span class="p">[</span><span class="n">j</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">arr</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">quick_sort</span><span class="p">(</span><span class="n">arr</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Quick sort implementation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">arr</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">pivot</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">left</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">arr</span> <span class="k">if</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">pivot</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">middle</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">arr</span> <span class="k">if</span> <span class="n">x</span> <span class="o">==</span> <span class="n">pivot</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">right</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">arr</span> <span class="k">if</span> <span class="n">x</span> <span class="o">&gt;</span> <span class="n">pivot</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">quick_sort</span><span class="p">(</span><span class="n">left</span><span class="p">)</span> <span class="o">+</span> <span class="n">middle</span> <span class="o">+</span> <span class="n">quick_sort</span><span class="p">(</span><span class="n">right</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">profile_sorting</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Profile and compare sorting algorithms&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10000</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># Profile bubble sort</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">bubble_sort</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Bubble Sort Profile:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">TIME</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="c1"># Profile quick sort</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">quick_sort</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Quick Sort Profile:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">TIME</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">profile_sorting</span><span class="p">()</span></span></span></code></pre></div><h3 id="進階練習用-line_profiler-找出熱點程式碼">進階練習：用 line_profiler 找出熱點程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">Exercise 2: Use line_profiler to find hotspots
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">Instructions:
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">1. Install line_profiler: pip install line_profiler
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">2. Add @profile decorator to functions you want to analyze
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">3. Run: kernprof -l -v exercise2.py
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">from</span> <span class="nn">line_profiler</span> <span class="kn">import</span> <span class="n">profile</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@profile</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">find_primes</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Find all prime numbers up to n&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">primes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">is_prime</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">num</span> <span class="o">**</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="n">num</span> <span class="o">%</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                <span class="n">is_prime</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="n">is_prime</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">primes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">primes</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nd">@profile</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">def</span> <span class="nf">find_primes_sieve</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Find primes using Sieve of Eratosthenes&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">sieve</span> <span class="o">=</span> <span class="p">[</span><span class="kc">True</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">sieve</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">sieve</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nb">int</span><span class="p">(</span><span class="n">n</span> <span class="o">**</span> <span class="mf">0.5</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">if</span> <span class="n">sieve</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">i</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">sieve</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">is_prime</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">sieve</span><span class="p">)</span> <span class="k">if</span> <span class="n">is_prime</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Finding primes up to 10000...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">primes1</span> <span class="o">=</span> <span class="n">find_primes</span><span class="p">(</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="n">primes2</span> <span class="o">=</span> <span class="n">find_primes_sieve</span><span class="p">(</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">primes1</span><span class="p">)</span><span class="si">}</span><span class="s2"> primes (basic)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">primes2</span><span class="p">)</span><span class="si">}</span><span class="s2"> primes (sieve)&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題比較不同正則表達式寫法的效能差異">挑戰題：比較不同正則表達式寫法的效能差異</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">Exercise 3: Compare regex pattern performance
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">Task: Parse email addresses from text using different patterns
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">and measure their performance.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_email_patterns</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Compare different email regex patterns&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Test content with mixed valid and invalid emails</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">test_text</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    Contact us at support@example.com or sales@company.org
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Invalid: not.an.email, @missing.com, missing@
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Valid: user.name+tag@domain.co.uk, test123@sub.domain.com
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Edge cases: &#34;quoted&#34;@domain.com, user@[192.168.1.1]
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span> <span class="o">*</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">patterns</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="c1"># Simple pattern (may miss some valid emails)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;simple&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\b[\w.-]+@[\w.-]+\.\w+\b&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># More comprehensive pattern</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;comprehensive&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b&#39;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="c1"># RFC 5322 inspired (complex but thorough)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;rfc_inspired&#34;</span><span class="p">:</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;(?:[a-z0-9!#$%&amp;</span><span class="se">\&#39;</span><span class="s1">*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;</span><span class="se">\&#39;</span><span class="s1">*+/=?^_`{|}~-]+)*&#39;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;|&#34;(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]&#39;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;|</span><span class="se">\\</span><span class="s1">[\x01-\x09\x0b\x0c\x0e-\x7f])*&#34;)&#39;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;@(?:(?:[a-z0-9](/python-advanced/04-cpython-internals/case-studies/profiling/?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](/python-advanced/04-cpython-internals/case-studies/profiling/?:[a-z0-9-]*[a-z0-9])?&#39;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.)</span><span class="si">{3}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:&#39;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]&#39;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="sa">r</span><span class="s1">&#39;|</span><span class="se">\\</span><span class="s1">[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Email Pattern Performance Comparison&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Pattern&#39;</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Time (ms)&#39;</span><span class="si">:</span><span class="s2">&lt;12</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="s1">&#39;Matches&#39;</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="c1"># Warmup</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="nb">list</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="n">test_text</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="n">test_text</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">&lt;12.2f</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">matches</span><span class="p">)</span><span class="si">:</span><span class="s2">&lt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="c1"># Your task: Add more patterns and analyze the results</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="c1"># Consider: What trade-offs exist between accuracy and speed?</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="n">benchmark_email_patterns</span><span class="p">()</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/profile.html">cProfile 官方文件</a></li>
<li><a href="https://github.com/pyutils/line_profiler">line_profiler GitHub</a></li>
<li><a href="https://docs.python.org/3/howto/regex.html#common-problems">Python 正則表達式效能</a></li>
<li><a href="https://github.com/benfred/py-spy">py-spy - Sampling profiler</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/" data-link-title="案例：記憶體優化" data-link-desc="用 __slots__ 和 weakref 優化快取系統的記憶體使用">記憶體優化</a></p>
]]></content:encoded></item><item><title>T.C2 Auth handshake 邏輯缺失被 FakeWebSocketChannel 遮蔽</title><link>https://tarrragon.github.io/blog/testing/cases/auth-handshake-missing-mock-blindspot/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/auth-handshake-missing-mock-blindspot/</guid><description>&lt;p>這個案例的核心責任是說明 mock 如何讓「功能缺失」變得不可見。不同於 T.C1（功能存在但行為錯誤），這個案例是功能根本沒實作 — 因為 mock 不需要這個功能就能通過所有 test。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>ttyd WebSocket 協議要求連線建立後發送一個 JSON frame 包含 base64 編碼的帳密（&lt;code>{&amp;quot;AuthToken&amp;quot;:&amp;quot;base64(user:pass)&amp;quot;}&lt;/code>），ttyd 驗證通過後才開始推送 terminal output。app_tunnel 的 &lt;code>ConnectionManager&lt;/code> 建立 WS 連線後直接開始監聽 stream，沒有發送 auth token。&lt;/p>
&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>連線建立後 ttyd 不推送資料（等 auth token），app 顯示空白終端機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Unit test 結果&lt;/td>
 &lt;td>10 個 ConnectionManager test 全過（&lt;code>FakeWebSocketChannel.ready&lt;/code> 立即完成）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Integration test 結果&lt;/td>
 &lt;td>11 個 connection_flow_test 全過（同樣用 &lt;code>FakeWebSocketChannel&lt;/code>）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實機表現&lt;/td>
 &lt;td>連線成功，終端機空白無輸出&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復&lt;/td>
 &lt;td>新增 &lt;code>_sendAuthTokenIfNeeded()&lt;/code> 在 &lt;code>_establishWebSocket()&lt;/code> 內呼叫&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Mock 的 happy path 比真實服務寬鬆&lt;/strong>。&lt;code>FakeWebSocketChannel&lt;/code> 的 &lt;code>ready&lt;/code> 是 &lt;code>Future.value()&lt;/code>（立即完成），&lt;code>stream&lt;/code> 是開發者手動控制的 &lt;code>StreamController&lt;/code>。真實 ttyd 的行為是：&lt;code>ready&lt;/code> 完成代表 TCP+WS 握手成功，但 stream 要等 auth token 驗證後才有資料。Mock 把兩步合成一步。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Integration test 名為整合實為 fake&lt;/strong>。&lt;code>connection_flow_test.dart&lt;/code> 標題是「端對端整合測試」，但內部使用 &lt;code>FakeWebSocketChannel&lt;/code> + &lt;code>FakeBiometricService&lt;/code> + &lt;code>InMemoryCredentialRepository&lt;/code> — 三個核心依賴全是 fake。這個 test 驗證的是「假設所有外部服務都正常，內部狀態機是否正確」，不是「真實服務互動是否正確」。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>功能缺失比功能錯誤更難被 test 抓到&lt;/strong>。功能錯誤（T.C1 text vs binary）至少有一個實作可以斷言；功能缺失意味著沒有程式碼可以 test。只有 protocol integration test（對真實服務跑）才能暴露「應該有但沒有」的行為。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Protocol integration test 必須涵蓋 auth handshake&lt;/strong>：連線 → 發送正確 auth token → 斷言收到 output；連線 → 不發送 auth token → 斷言 timeout 或斷線。&lt;/li>
&lt;li>&lt;strong>在企劃階段列出協議握手步驟&lt;/strong>：ttyd WS 協議的 auth handshake 應該在 spec 文件中明確列出，不依賴開發者記得實作。&lt;/li>
&lt;li>&lt;strong>區分「名義 integration」和「真實 integration」&lt;/strong>：test 名稱含 integration 但全用 fake，應標明 &lt;code>fake-integration&lt;/code> 或改名 &lt;code>connection-state-machine-test&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想區分 mock 層級 → &lt;a href="https://tarrragon.github.io/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">模組一：測試策略分層&lt;/a>&lt;/li>
&lt;li>想建 protocol integration test → &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試&lt;/a>&lt;/li>
&lt;li>想設計 auth 機制的 UX fallback → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2 biometricOnly 無 fallback&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 mock 如何讓「功能缺失」變得不可見。不同於 T.C1（功能存在但行為錯誤），這個案例是功能根本沒實作 — 因為 mock 不需要這個功能就能通過所有 test。</p>
<h2 id="觀察">觀察</h2>
<p>ttyd WebSocket 協議要求連線建立後發送一個 JSON frame 包含 base64 編碼的帳密（<code>{&quot;AuthToken&quot;:&quot;base64(user:pass)&quot;}</code>），ttyd 驗證通過後才開始推送 terminal output。app_tunnel 的 <code>ConnectionManager</code> 建立 WS 連線後直接開始監聽 stream，沒有發送 auth token。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>影響範圍</td>
          <td>連線建立後 ttyd 不推送資料（等 auth token），app 顯示空白終端機</td>
      </tr>
      <tr>
          <td>Unit test 結果</td>
          <td>10 個 ConnectionManager test 全過（<code>FakeWebSocketChannel.ready</code> 立即完成）</td>
      </tr>
      <tr>
          <td>Integration test 結果</td>
          <td>11 個 connection_flow_test 全過（同樣用 <code>FakeWebSocketChannel</code>）</td>
      </tr>
      <tr>
          <td>實機表現</td>
          <td>連線成功，終端機空白無輸出</td>
      </tr>
      <tr>
          <td>修復</td>
          <td>新增 <code>_sendAuthTokenIfNeeded()</code> 在 <code>_establishWebSocket()</code> 內呼叫</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>Mock 的 happy path 比真實服務寬鬆</strong>。<code>FakeWebSocketChannel</code> 的 <code>ready</code> 是 <code>Future.value()</code>（立即完成），<code>stream</code> 是開發者手動控制的 <code>StreamController</code>。真實 ttyd 的行為是：<code>ready</code> 完成代表 TCP+WS 握手成功，但 stream 要等 auth token 驗證後才有資料。Mock 把兩步合成一步。</p>
</li>
<li>
<p><strong>Integration test 名為整合實為 fake</strong>。<code>connection_flow_test.dart</code> 標題是「端對端整合測試」，但內部使用 <code>FakeWebSocketChannel</code> + <code>FakeBiometricService</code> + <code>InMemoryCredentialRepository</code> — 三個核心依賴全是 fake。這個 test 驗證的是「假設所有外部服務都正常，內部狀態機是否正確」，不是「真實服務互動是否正確」。</p>
</li>
<li>
<p><strong>功能缺失比功能錯誤更難被 test 抓到</strong>。功能錯誤（T.C1 text vs binary）至少有一個實作可以斷言；功能缺失意味著沒有程式碼可以 test。只有 protocol integration test（對真實服務跑）才能暴露「應該有但沒有」的行為。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>Protocol integration test 必須涵蓋 auth handshake</strong>：連線 → 發送正確 auth token → 斷言收到 output；連線 → 不發送 auth token → 斷言 timeout 或斷線。</li>
<li><strong>在企劃階段列出協議握手步驟</strong>：ttyd WS 協議的 auth handshake 應該在 spec 文件中明確列出，不依賴開發者記得實作。</li>
<li><strong>區分「名義 integration」和「真實 integration」</strong>：test 名稱含 integration 但全用 fake，應標明 <code>fake-integration</code> 或改名 <code>connection-state-machine-test</code>。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想區分 mock 層級 → <a href="/blog/testing/01-test-strategy-layers/" data-link-title="模組一：測試策略分層" data-link-desc="Unit / Protocol Integration / Screen State 三層測試各自的職責、盲區和判斷原則">模組一：測試策略分層</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>
<li>想設計 auth 機制的 UX fallback → <a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2 biometricOnly 無 fallback</a></li>
</ul>
]]></content:encoded></item><item><title>U.C2 biometricOnly=true 無密碼 fallback</title><link>https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/</guid><description>&lt;p>這個案例的核心責任是說明 Gate（使用者必須通過的關卡）的設計不只是「成功時怎麼做」，還必須包含「失敗時的替代路徑」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 使用 &lt;code>local_auth&lt;/code> 套件進行生物辨識認證。&lt;code>AuthenticationOptions&lt;/code> 設定 &lt;code>biometricOnly: true&lt;/code>，表示只接受生物辨識（Face ID / 指紋），不接受裝置密碼作為 fallback。&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">// 修復前
&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="nl">options:&lt;/span> &lt;span class="kd">const&lt;/span> &lt;span class="n">AuthenticationOptions&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="nl">stickyAuth:&lt;/span> &lt;span class="kc">true&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="nl">biometricOnly:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Face ID 不可用 → 認證直接失敗
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&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">// 修復後
&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="nl">options:&lt;/span> &lt;span class="kd">const&lt;/span> &lt;span class="n">AuthenticationOptions&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="nl">stickyAuth:&lt;/span> &lt;span class="kc">true&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="nl">biometricOnly:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Face ID 不可用 → 系統自動提示輸入裝置密碼
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">),&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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>Face ID 不可用時（戴口罩、光線差、指紋模糊、模擬器）完全無法使用 app&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復成本&lt;/td>
 &lt;td>改一個 boolean&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>根因&lt;/td>
 &lt;td>企劃階段未設計 biometric gate 的 fallback&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Gate fallback 是設計問題，不是實作問題&lt;/strong>。&lt;code>biometricOnly&lt;/code> 的預設值是 &lt;code>false&lt;/code>（允許密碼 fallback），開發時特意改成 &lt;code>true&lt;/code> 是因為認為「安全性更高」。但這個判斷沒有考慮 fallback 缺失時的 UX 代價 — 使用者完全無法進入 app。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>開發環境遮蔽了問題&lt;/strong>。iOS 模擬器預設不支援 Face ID，但 &lt;code>isAvailable()&lt;/code> 的實作會檢查 &lt;code>isDeviceSupported()&lt;/code> + &lt;code>getAvailableBiometrics().isNotEmpty&lt;/code>。模擬器回傳 &lt;code>isDeviceSupported() = true&lt;/code> 但 &lt;code>getAvailableBiometrics() = []&lt;/code>，所以在模擬器上 &lt;code>isAvailable()&lt;/code> 回傳 false，直接跳過認證走預設路徑。真實裝置上 &lt;code>isAvailable() = true&lt;/code> 但 Face ID 可能失敗，這時沒有 fallback。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>安全性 vs 可用性的取捨需要顯式記錄&lt;/strong>。&lt;code>biometricOnly: true&lt;/code> 的安全收益是「確保只有生物特徵擁有者能操作」；代價是「任何生物辨識失敗場景都阻擋使用」。自用工具的使用者就是 owner，密碼 fallback 的安全風險遠低於「完全無法使用」的可用性風險。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>每個 gate 設計時列三問&lt;/strong>：成功時做什麼？失敗時做什麼？使用者不知道發生什麼時做什麼？&lt;/li>
&lt;li>&lt;strong>在狀態矩陣標注 gate fallback&lt;/strong>：biometric / network / auth 每個 gate 旁邊標注替代路徑，空白 = 使用者被擋住。&lt;/li>
&lt;li>&lt;strong>安全 vs 可用性取捨顯式記錄&lt;/strong>：在 spec 文件記錄「&lt;code>biometricOnly: false&lt;/code> — 接受密碼 fallback，因為自用工具可用性優先於生物辨識強制」。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計 Gate fallback 體系 → &lt;a href="https://tarrragon.github.io/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法&lt;/a>&lt;/li>
&lt;li>想了解 biometric 在不同平台的行為差異 → 待補：iOS/Android biometric API 行為對照&lt;/li>
&lt;li>類似案例（導航死胡同）→ &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五個狀態零個退出&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Gate（使用者必須通過的關卡）的設計不只是「成功時怎麼做」，還必須包含「失敗時的替代路徑」。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 使用 <code>local_auth</code> 套件進行生物辨識認證。<code>AuthenticationOptions</code> 設定 <code>biometricOnly: true</code>，表示只接受生物辨識（Face ID / 指紋），不接受裝置密碼作為 fallback。</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">// 修復前
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nl">options:</span> <span class="kd">const</span> <span class="n">AuthenticationOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nl">stickyAuth:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nl">biometricOnly:</span> <span class="kc">true</span><span class="p">,</span>  <span class="c1">// Face ID 不可用 → 認證直接失敗
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><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">// 修復後
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="nl">options:</span> <span class="kd">const</span> <span class="n">AuthenticationOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nl">stickyAuth:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nl">biometricOnly:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// Face ID 不可用 → 系統自動提示輸入裝置密碼
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="p">),</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>影響範圍</td>
          <td>Face ID 不可用時（戴口罩、光線差、指紋模糊、模擬器）完全無法使用 app</td>
      </tr>
      <tr>
          <td>修復成本</td>
          <td>改一個 boolean</td>
      </tr>
      <tr>
          <td>根因</td>
          <td>企劃階段未設計 biometric gate 的 fallback</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>Gate fallback 是設計問題，不是實作問題</strong>。<code>biometricOnly</code> 的預設值是 <code>false</code>（允許密碼 fallback），開發時特意改成 <code>true</code> 是因為認為「安全性更高」。但這個判斷沒有考慮 fallback 缺失時的 UX 代價 — 使用者完全無法進入 app。</p>
</li>
<li>
<p><strong>開發環境遮蔽了問題</strong>。iOS 模擬器預設不支援 Face ID，但 <code>isAvailable()</code> 的實作會檢查 <code>isDeviceSupported()</code> + <code>getAvailableBiometrics().isNotEmpty</code>。模擬器回傳 <code>isDeviceSupported() = true</code> 但 <code>getAvailableBiometrics() = []</code>，所以在模擬器上 <code>isAvailable()</code> 回傳 false，直接跳過認證走預設路徑。真實裝置上 <code>isAvailable() = true</code> 但 Face ID 可能失敗，這時沒有 fallback。</p>
</li>
<li>
<p><strong>安全性 vs 可用性的取捨需要顯式記錄</strong>。<code>biometricOnly: true</code> 的安全收益是「確保只有生物特徵擁有者能操作」；代價是「任何生物辨識失敗場景都阻擋使用」。自用工具的使用者就是 owner，密碼 fallback 的安全風險遠低於「完全無法使用」的可用性風險。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>每個 gate 設計時列三問</strong>：成功時做什麼？失敗時做什麼？使用者不知道發生什麼時做什麼？</li>
<li><strong>在狀態矩陣標注 gate fallback</strong>：biometric / network / auth 每個 gate 旁邊標注替代路徑，空白 = 使用者被擋住。</li>
<li><strong>安全 vs 可用性取捨顯式記錄</strong>：在 spec 文件記錄「<code>biometricOnly: false</code> — 接受密碼 fallback，因為自用工具可用性優先於生物辨識強制」。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 Gate fallback 體系 → <a href="/blog/ux-design/02-gate-fallback/gate-three-questions/" data-link-title="Gate 分類與三問設計法" data-link-desc="每個 gate 設計時問三個問題：成功時做什麼、失敗時做什麼、使用者不知道發生什麼時做什麼">Gate 分類與三問設計法</a></li>
<li>想了解 biometric 在不同平台的行為差異 → 待補：iOS/Android biometric API 行為對照</li>
<li>類似案例（導航死胡同）→ <a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五個狀態零個退出</a></li>
</ul>
]]></content:encoded></item><item><title>9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/</guid><description>&lt;p>這個案例的核心責任是說明「事件型不可預期峰值」的工程做法。體育博彩流量的形狀跟 Prime Day 不同 — 峰值會在賽事的特定瞬間（進球、最後一分鐘）爆量、單一賽事內可能有多次脈衝、跨賽事的時間點難以提前數月排程。GR8 Tech 在 2022 FIFA World Cup 期間達到零停機營運、是這類負載形狀的有效參考。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GR8 Tech 從本地基礎設施遷移到 AWS、重建為微服務架構後的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech case study&lt;/a>）：&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>投注延遲&lt;/td>
 &lt;td>賽事高峰期額外延遲 2-3 秒&lt;/td>
 &lt;td>25 ms p95&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結算吞吐&lt;/td>
 &lt;td>（未公開）&lt;/td>
 &lt;td>每分鐘 100 萬次投注結算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>交易吞吐&lt;/td>
 &lt;td>（未公開）&lt;/td>
 &lt;td>54000 TPS @ 25ms p95&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同時在線&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>200,000+ 同時使用者&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>投注吞吐&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>每分鐘 80,000 次體育投注&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>99.95% uptime&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本彈性&lt;/td>
 &lt;td>固定預配置&lt;/td>
 &lt;td>需求降低時成本下降 25%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon EKS（Kubernetes 容器編排、跨雲端與本地）、Amazon EC2（compute）、Amazon S3 與 Amazon EBS（儲存）、AWS Auto Scaling 結合 &lt;strong>GR8 Tech 自家 AI 預測模型&lt;/strong>、AWS Infrastructure Event Management（重大賽事支援）。&lt;/p>
&lt;p>擴展範圍：「Scaled to 15 markets using AWS」。事件覆蓋：2022 FIFA World Cup 期間零停機。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>GR8 Tech 的工程做法揭露三個事件型峰值的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>不可預期 ≠ 不可預測&lt;/strong>：賽事「何時開打」是已知的（schedule 提前公告）、「賽事內何時爆量」是未知的（進球、加時、最後一分鐘）。AI 預測模型不是預測「會不會有峰值」、而是預測「峰值在 60 秒內可能多大」、把擴容窗口縮短到反應時間之內。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的「預測時間尺度」軸。&lt;/li>
&lt;li>&lt;strong>延遲是業務指標、不是技術指標&lt;/strong>：「2-3 秒額外延遲」直接造成「投注失敗、客戶流失」。25ms p95 是收入 KPI 而不是 SLO 漂亮數字。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 把 latency 翻成業務 metric 的責任。&lt;/li>
&lt;li>&lt;strong>微服務 + 容器編排是擴容粒度的前置&lt;/strong>：遷移前的單體系統「擴容」只能複製整套系統、成本曲線陡峭。EKS 拆解後可以針對熱點服務（投注引擎、結算引擎）獨立擴容、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的逐層定位直接對齊。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：54000 TPS @ 25ms 是 &lt;em>公開的成功數字&lt;/em>、不是「永遠都這樣」的承諾。AI 預測模型必然有預測誤差、AWS Infrastructure Event Management 也是事件型服務、不是平台預設。這類案例適合作為「目標可達性」的存在證明、不適合直接套用為自家服務的容量假設。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>把賽事 schedule 灌進 capacity forecast&lt;/strong>：在事件已知的條件下、預先把 baseline 拉高、避免 AI 模型在零起跑時擴容。對應 EC2 Auto Scaling 的 &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">scheduled scaling&lt;/a> + predictive scaling 雙模。&lt;/li>
&lt;li>&lt;strong>AI 模型輸入要包含領域訊號&lt;/strong>：通用 ML autoscaler 用 CPU / latency 預測、領域 autoscaler 還會用 &lt;em>賽事重要性&lt;/em>、&lt;em>投注量歷史曲線&lt;/em>、&lt;em>下注玩家集中度&lt;/em> 等業務訊號。這層讓擴容時機從反應式變成預測式。&lt;/li>
&lt;li>&lt;strong>熱點服務獨立擴容、不是整體擴容&lt;/strong>：投注引擎跟結算引擎的峰值時間不一致（投注集中在賽前 + 比賽中、結算集中在賽後）、單獨擴容比整體擴容省 25%+ 成本。&lt;/li>
&lt;li>&lt;strong>AWS Infrastructure Event Management 等廠商支援服務&lt;/strong>：在年度重大事件可以申請（World Cup、Olympic、Black Friday 等）、提供 pre-scaling 與專屬監控通道。這在 GCP / Azure 也有對等服務（GCP Customer Care Premium、Azure Event Management Support）。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE + Vertical Pod Autoscaler + 自家 ML 預測、Azure AKS + KEDA + Azure ML 預測、自建 Kubernetes + Karpenter + Prometheus 推導模型都可以實作同樣的「預測 + 擴容」模式。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「事件型不可預期峰值」的工程做法。體育博彩流量的形狀跟 Prime Day 不同 — 峰值會在賽事的特定瞬間（進球、最後一分鐘）爆量、單一賽事內可能有多次脈衝、跨賽事的時間點難以提前數月排程。GR8 Tech 在 2022 FIFA World Cup 期間達到零停機營運、是這類負載形狀的有效參考。</p>
<h2 id="觀察">觀察</h2>
<p>GR8 Tech 從本地基礎設施遷移到 AWS、重建為微服務架構後的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>遷移前狀況</th>
          <th>遷移後峰值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>投注延遲</td>
          <td>賽事高峰期額外延遲 2-3 秒</td>
          <td>25 ms p95</td>
      </tr>
      <tr>
          <td>結算吞吐</td>
          <td>（未公開）</td>
          <td>每分鐘 100 萬次投注結算</td>
      </tr>
      <tr>
          <td>交易吞吐</td>
          <td>（未公開）</td>
          <td>54000 TPS @ 25ms p95</td>
      </tr>
      <tr>
          <td>同時在線</td>
          <td>-</td>
          <td>200,000+ 同時使用者</td>
      </tr>
      <tr>
          <td>投注吞吐</td>
          <td>-</td>
          <td>每分鐘 80,000 次體育投注</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>-</td>
          <td>99.95% uptime</td>
      </tr>
      <tr>
          <td>成本彈性</td>
          <td>固定預配置</td>
          <td>需求降低時成本下降 25%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon EKS（Kubernetes 容器編排、跨雲端與本地）、Amazon EC2（compute）、Amazon S3 與 Amazon EBS（儲存）、AWS Auto Scaling 結合 <strong>GR8 Tech 自家 AI 預測模型</strong>、AWS Infrastructure Event Management（重大賽事支援）。</p>
<p>擴展範圍：「Scaled to 15 markets using AWS」。事件覆蓋：2022 FIFA World Cup 期間零停機。</p>
<h2 id="判讀">判讀</h2>
<p>GR8 Tech 的工程做法揭露三個事件型峰值的判讀重點。</p>
<ol>
<li><strong>不可預期 ≠ 不可預測</strong>：賽事「何時開打」是已知的（schedule 提前公告）、「賽事內何時爆量」是未知的（進球、加時、最後一分鐘）。AI 預測模型不是預測「會不會有峰值」、而是預測「峰值在 60 秒內可能多大」、把擴容窗口縮短到反應時間之內。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的「預測時間尺度」軸。</li>
<li><strong>延遲是業務指標、不是技術指標</strong>：「2-3 秒額外延遲」直接造成「投注失敗、客戶流失」。25ms p95 是收入 KPI 而不是 SLO 漂亮數字。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 把 latency 翻成業務 metric 的責任。</li>
<li><strong>微服務 + 容器編排是擴容粒度的前置</strong>：遷移前的單體系統「擴容」只能複製整套系統、成本曲線陡峭。EKS 拆解後可以針對熱點服務（投注引擎、結算引擎）獨立擴容、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的逐層定位直接對齊。</li>
</ol>
<p>需要警惕的判讀盲點：54000 TPS @ 25ms 是 <em>公開的成功數字</em>、不是「永遠都這樣」的承諾。AI 預測模型必然有預測誤差、AWS Infrastructure Event Management 也是事件型服務、不是平台預設。這類案例適合作為「目標可達性」的存在證明、不適合直接套用為自家服務的容量假設。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>把賽事 schedule 灌進 capacity forecast</strong>：在事件已知的條件下、預先把 baseline 拉高、避免 AI 模型在零起跑時擴容。對應 EC2 Auto Scaling 的 <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">scheduled scaling</a> + predictive scaling 雙模。</li>
<li><strong>AI 模型輸入要包含領域訊號</strong>：通用 ML autoscaler 用 CPU / latency 預測、領域 autoscaler 還會用 <em>賽事重要性</em>、<em>投注量歷史曲線</em>、<em>下注玩家集中度</em> 等業務訊號。這層讓擴容時機從反應式變成預測式。</li>
<li><strong>熱點服務獨立擴容、不是整體擴容</strong>：投注引擎跟結算引擎的峰值時間不一致（投注集中在賽前 + 比賽中、結算集中在賽後）、單獨擴容比整體擴容省 25%+ 成本。</li>
<li><strong>AWS Infrastructure Event Management 等廠商支援服務</strong>：在年度重大事件可以申請（World Cup、Olympic、Black Friday 等）、提供 pre-scaling 與專屬監控通道。這在 GCP / Azure 也有對等服務（GCP Customer Care Premium、Azure Event Management Support）。</li>
</ol>
<p>跨平台等效：GCP GKE + Vertical Pod Autoscaler + 自家 ML 預測、Azure AKS + KEDA + Azure ML 預測、自建 Kubernetes + Karpenter + Prometheus 推導模型都可以實作同樣的「預測 + 擴容」模式。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做事件型峰值的容量預測 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想用 AI / ML 做預測式擴容 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a></li>
<li>想拆解微服務以便獨立擴容 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>對照不同形狀的峰值 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a>（可預期極端峰值）/ <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a>（無峰值低延遲）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech Achieves High Performance and Scalability with Data Center Migration to AWS</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">Predictive scaling for Amazon EC2 Auto Scaling</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">Scheduled scaling for Amazon EC2 Auto Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>2.C2 Meta：mcrouter 與跨區快取路由</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-mcrouter-global-cache-routing/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-mcrouter-global-cache-routing/</guid><description>&lt;p>這個案例的核心責任是說明快取規模變大後，路由層本身會成為選型主題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>mcrouter 被用來統一處理大量 memcached 流量與跨叢集路由，代表快取已從局部優化變成平台層能力。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當快取服務跨區、跨叢集且請求量極高時，應把路由策略、故障切換與運維一致性視為主議題。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把 client 端散落邏輯收斂到路由層。&lt;/li>
&lt;li>把跨區路由與故障策略標準化。&lt;/li>
&lt;li>用可觀測訊號監控路由品質與新鮮度。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1 高併發 Redis 邊界&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/service-discovery/" data-link-title="5.4 service discovery" data-link-desc="整理 endpoint discovery 與 DNS">5.4 service discovery&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2014/09/15/web/introducing-mcrouter-a-memcached-protocol-router-for-scaling-memcached-deployments/">Introducing mcrouter&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取規模變大後，路由層本身會成為選型主題。</p>
<h2 id="觀察">觀察</h2>
<p>mcrouter 被用來統一處理大量 memcached 流量與跨叢集路由，代表快取已從局部優化變成平台層能力。</p>
<h2 id="判讀">判讀</h2>
<p>當快取服務跨區、跨叢集且請求量極高時，應把路由策略、故障切換與運維一致性視為主議題。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把 client 端散落邏輯收斂到路由層。</li>
<li>把跨區路由與故障策略標準化。</li>
<li>用可觀測訊號監控路由品質與新鮮度。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1 高併發 Redis 邊界</a> 與 <a href="/blog/backend/05-deployment-platform/service-discovery/" data-link-title="5.4 service discovery" data-link-desc="整理 endpoint discovery 與 DNS">5.4 service discovery</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2014/09/15/web/introducing-mcrouter-a-memcached-protocol-router-for-scaling-memcached-deployments/">Introducing mcrouter</a></li>
</ul>
]]></content:encoded></item><item><title>3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/</guid><description>&lt;p>這個案例的核心責任是把 broker 遷移拆成平台責任、運維責任與資料責任三層。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>CloudHealth 由自管 Kafka 遷移到 Amazon MSK，過程涵蓋 topic、存取控制、觀測與遷移執行節奏。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這類轉換的實際風險在 ACL、topic policy、client 相容性與 cutover 節奏，服務名稱本身反而是次要問題。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立新叢集治理基線（ACL、觀測、部署）。&lt;/li>
&lt;li>分批 topic 遷移並持續監測 lag/錯誤。&lt;/li>
&lt;li>把回退與流量切換條件寫成明確門檻。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/how-vmware-tanzu-cloudhealth-migrated-from-self-managed-kafka-to-amazon-msk/">VMware CloudHealth Kafka to MSK&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 broker 遷移拆成平台責任、運維責任與資料責任三層。</p>
<h2 id="觀察">觀察</h2>
<p>CloudHealth 由自管 Kafka 遷移到 Amazon MSK，過程涵蓋 topic、存取控制、觀測與遷移執行節奏。</p>
<h2 id="判讀">判讀</h2>
<p>這類轉換的實際風險在 ACL、topic policy、client 相容性與 cutover 節奏，服務名稱本身反而是次要問題。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立新叢集治理基線（ACL、觀測、部署）。</li>
<li>分批 topic 遷移並持續監測 lag/錯誤。</li>
<li>把回退與流量切換條件寫成明確門檻。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a> 與 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/how-vmware-tanzu-cloudhealth-migrated-from-self-managed-kafka-to-amazon-msk/">VMware CloudHealth Kafka to MSK</a></li>
</ul>
]]></content:encoded></item><item><title>5.C2 Condé Nast：EKS 平台整併與標準化</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/</guid><description>&lt;p>這個案例的核心責任是說明平台整併常是組織治理問題，技術選型只是其中一層。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Condé Nast 旗下多個小團隊各自維護獨立的 Kubernetes 環境，各團隊使用不同的 Kubernetes 版本、操作模型、部署流程與存取模式。Self-managed Kubernetes 跑在 EC2 上，每個團隊自行維護 control plane、AMI、安全修補與 IAM credential 管理（使用 kube2iam 等開源工具）。&lt;/p>
&lt;p>整併後成立一個 single global platform team，遷移到 Amazon EKS。技術棧標準化為 Bottlerocket OS、VPC CNI、AWS Load Balancer Controller、IRSA（IAM Roles for Service Accounts）。Multi-tenancy 用 Kubernetes namespace 隔離，搭配 resource quotas 與 limits 防止 noisy neighbor。&lt;/p>
&lt;p>結果面：搭配 CloudFront 與 AWS Global Accelerator 後，end user latency 降低達 50%。團隊可以在 guardrails 內快速建立新叢集，operational overhead 顯著降低。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>平台碎片化的代價分兩層。表面層是重工——每個團隊各自處理安全修補、版本升級、credential 管理，相同工作做了 N 遍。深層是一致性缺失——不同團隊的安全基線不同，某個團隊漏修的 CVE 可能成為整個組織的入口。&lt;/p>
&lt;p>整併的工程價值在於把「每個團隊各自解決平台問題」變成「平台團隊解決一次、所有團隊共用」。這個轉換的前提是平台團隊能提供足夠彈性的 multi-tenancy 模型——resource quotas 防止資源搶占、namespace 隔離防止互相影響、IRSA 讓每個 workload 有獨立的 AWS 權限而非共用 node-level credential。&lt;/p>
&lt;p>kube2iam → IRSA 的切換是這個案例中安全基線提升最顯著的一步。kube2iam 依賴 iptables 攔截 metadata endpoint，在多租戶環境下有 race condition 與 credential leak 風險。IRSA 用 OIDC federation 讓每個 service account 直接取得 scoped IAM role，消除了 node-level 的 credential 共用。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>盤點既有叢集的差異維度&lt;/strong>：Kubernetes 版本、CNI、ingress controller、credential 管理方式、部署流程、監控工具。差異清單是遷移計畫的輸入。&lt;/li>
&lt;li>&lt;strong>定義統一平台基線&lt;/strong>：選定 EKS + Bottlerocket + VPC CNI + IRSA 作為所有叢集的共通配置。基線要涵蓋安全（pod 唯讀 filesystem、禁 root）、資源（quotas、limits）、網路（CNI、LB controller）。&lt;/li>
&lt;li>&lt;strong>用 namespace multi-tenancy 取代獨立叢集&lt;/strong>：每個團隊一個 namespace，resource quotas 限制資源用量。這比一個團隊一個叢集的運維成本低，但需要在 namespace 層級做好隔離（NetworkPolicy、ResourceQuota、RBAC scope）。&lt;/li>
&lt;li>&lt;strong>漸進切換業務流量&lt;/strong>：按 region / 市場分批遷移，每批遷移後驗證 latency 與 error rate。搭配 CloudFront 做 edge 層的流量管理。&lt;/li>
&lt;/ol>
&lt;h2 id="可回寫的章節段落">可回寫的章節段落&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%a4%a7%e8%a6%8f%e6%a8%a1-k8s-%e7%9a%84%e8%a8%ad%e8%a8%88%e5%8f%96%e6%8d%a8" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 大規模 K8s 的設計取捨&lt;/a>：single-cluster multi-namespace 的治理單位選擇&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 Managed 平台跟團隊職責邊界&lt;/a>：global platform team 的職責重訂&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/load-balancer-contract/" data-link-title="5.3 load balancer 合約" data-link-desc="整理 idle timeout、draining 與 health check">5.3 Load Balancer Contract&lt;/a>：AWS LB Controller + CloudFront 的流量入口配置&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/containers/how-conde-nast-modernized-its-container-platform-on-amazon-elastic-kubernetes-service/">How Condé Nast modernized its container platform on Amazon EKS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明平台整併常是組織治理問題，技術選型只是其中一層。</p>
<h2 id="觀察">觀察</h2>
<p>Condé Nast 旗下多個小團隊各自維護獨立的 Kubernetes 環境，各團隊使用不同的 Kubernetes 版本、操作模型、部署流程與存取模式。Self-managed Kubernetes 跑在 EC2 上，每個團隊自行維護 control plane、AMI、安全修補與 IAM credential 管理（使用 kube2iam 等開源工具）。</p>
<p>整併後成立一個 single global platform team，遷移到 Amazon EKS。技術棧標準化為 Bottlerocket OS、VPC CNI、AWS Load Balancer Controller、IRSA（IAM Roles for Service Accounts）。Multi-tenancy 用 Kubernetes namespace 隔離，搭配 resource quotas 與 limits 防止 noisy neighbor。</p>
<p>結果面：搭配 CloudFront 與 AWS Global Accelerator 後，end user latency 降低達 50%。團隊可以在 guardrails 內快速建立新叢集，operational overhead 顯著降低。</p>
<h2 id="判讀">判讀</h2>
<p>平台碎片化的代價分兩層。表面層是重工——每個團隊各自處理安全修補、版本升級、credential 管理，相同工作做了 N 遍。深層是一致性缺失——不同團隊的安全基線不同，某個團隊漏修的 CVE 可能成為整個組織的入口。</p>
<p>整併的工程價值在於把「每個團隊各自解決平台問題」變成「平台團隊解決一次、所有團隊共用」。這個轉換的前提是平台團隊能提供足夠彈性的 multi-tenancy 模型——resource quotas 防止資源搶占、namespace 隔離防止互相影響、IRSA 讓每個 workload 有獨立的 AWS 權限而非共用 node-level credential。</p>
<p>kube2iam → IRSA 的切換是這個案例中安全基線提升最顯著的一步。kube2iam 依賴 iptables 攔截 metadata endpoint，在多租戶環境下有 race condition 與 credential leak 風險。IRSA 用 OIDC federation 讓每個 service account 直接取得 scoped IAM role，消除了 node-level 的 credential 共用。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>盤點既有叢集的差異維度</strong>：Kubernetes 版本、CNI、ingress controller、credential 管理方式、部署流程、監控工具。差異清單是遷移計畫的輸入。</li>
<li><strong>定義統一平台基線</strong>：選定 EKS + Bottlerocket + VPC CNI + IRSA 作為所有叢集的共通配置。基線要涵蓋安全（pod 唯讀 filesystem、禁 root）、資源（quotas、limits）、網路（CNI、LB controller）。</li>
<li><strong>用 namespace multi-tenancy 取代獨立叢集</strong>：每個團隊一個 namespace，resource quotas 限制資源用量。這比一個團隊一個叢集的運維成本低，但需要在 namespace 層級做好隔離（NetworkPolicy、ResourceQuota、RBAC scope）。</li>
<li><strong>漸進切換業務流量</strong>：按 region / 市場分批遷移，每批遷移後驗證 latency 與 error rate。搭配 CloudFront 做 edge 層的流量管理。</li>
</ol>
<h2 id="可回寫的章節段落">可回寫的章節段落</h2>
<ul>
<li><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%a4%a7%e8%a6%8f%e6%a8%a1-k8s-%e7%9a%84%e8%a8%ad%e8%a8%88%e5%8f%96%e6%8d%a8" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 大規模 K8s 的設計取捨</a>：single-cluster multi-namespace 的治理單位選擇</li>
<li><a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 Managed 平台跟團隊職責邊界</a>：global platform team 的職責重訂</li>
<li><a href="/blog/backend/05-deployment-platform/load-balancer-contract/" data-link-title="5.3 load balancer 合約" data-link-desc="整理 idle timeout、draining 與 health check">5.3 Load Balancer Contract</a>：AWS LB Controller + CloudFront 的流量入口配置</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/containers/how-conde-nast-modernized-its-container-platform-on-amazon-elastic-kubernetes-service/">How Condé Nast modernized its container platform on Amazon EKS</a></li>
</ul>
]]></content:encoded></item><item><title>7.C2 Cloudflare：2023 Control-plane Token 事件</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-control-plane-token-2023/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-control-plane-token-2023/</guid><description>&lt;p>這個案例的核心責任是把控制面 token 風險落到 secret lifecycle 與權限邊界治理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>控制面 token 事件顯示機器憑證若治理不足，會形成跨服務高權限風險。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這類問題的根因是 token 生命週期、最小權限與審計證據鏈未對齊，單一憑證洩漏只是觸發點。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>用工作負載身份替代長期共享 token。&lt;/li>
&lt;li>強制 token rotation 與細粒度 scope。&lt;/li>
&lt;li>把憑證事件寫入 release gate 與 incident triage。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6 secrets and machine credential governance&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 supply chain integrity&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/cloudflare-incident-on-january-24th-2023/">Cloudflare incident on January 24, 2023&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把控制面 token 風險落到 secret lifecycle 與權限邊界治理。</p>
<h2 id="觀察">觀察</h2>
<p>控制面 token 事件顯示機器憑證若治理不足，會形成跨服務高權限風險。</p>
<h2 id="判讀">判讀</h2>
<p>這類問題的根因是 token 生命週期、最小權限與審計證據鏈未對齊，單一憑證洩漏只是觸發點。</p>
<h2 id="策略">策略</h2>
<ol>
<li>用工作負載身份替代長期共享 token。</li>
<li>強制 token rotation 與細粒度 scope。</li>
<li>把憑證事件寫入 release gate 與 incident triage。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6 secrets and machine credential governance</a> 與 <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.12 supply chain integrity</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/cloudflare-incident-on-january-24th-2023/">Cloudflare incident on January 24, 2023</a></li>
</ul>
]]></content:encoded></item><item><title>AWS 2021 US-EAST-1 Control Plane Degradation</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2021-us-east-1-control-plane-degradation/</guid><description>&lt;p>2021 年 AWS us-east-1 事件的核心教訓是：控制面退化不一定來自服務程式錯誤，內部網路壓力也能讓 API 與依賴鏈條同時失真。這類事故要先確認控制面健康，再決定是否進行服務層回退。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>AWS 在 2021-12-07 發生 us-east-1 多服務退化事件。官方資訊指出，內部網路裝置的異常行為導致這個區域的 API 請求與內部服務通訊壅塞，進而造成多個服務管理與控制面能力受影響。部分資料面能力可用，但控制面操作、狀態回報與恢復節奏出現延遲。&lt;/p>
&lt;p>這類事故的難點在於，使用者看到的是「很多服務一起怪」，而工程上真正要先判斷的是：共同依賴是否先失真。&lt;/p>
&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>多服務 API 錯誤率同時上升&lt;/td>
 &lt;td>共享控制面或內部網路層可能失真&lt;/td>
 &lt;td>優先調查共用控制平面，不先分散逐服務排障&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>控制操作延遲遠高於資料讀寫&lt;/td>
 &lt;td>控制面與資料面可用性不同步&lt;/td>
 &lt;td>對外通訊要分清 control/data plane 差異&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>區域集中異常（us-east-1）&lt;/td>
 &lt;td>區域依賴與路由聚集形成單點風險&lt;/td>
 &lt;td>啟動跨區降載或備援策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>狀態更新節奏出現抖動&lt;/td>
 &lt;td>事故資訊供應鏈本身受影響&lt;/td>
 &lt;td>建立固定 cadence 與替代更新通道&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>區域內部網路層出現異常與壅塞。&lt;/li>
&lt;li>控制面 API 與內部依賴通訊受阻。&lt;/li>
&lt;li>多服務管理能力與狀態回報受到影響。&lt;/li>
&lt;li>部分服務資料面仍可運作，但操作與恢復節奏失真。&lt;/li>
&lt;li>團隊逐步收斂網路壓力並恢復控制面可用性。&lt;/li>
&lt;/ol>
&lt;p>這條路徑顯示：真正的擴散點在 shared internal network + control plane，不是某個單一服務程式。&lt;/p>
&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>Control/Data plane 分離判讀&lt;/td>
 &lt;td>對外敘述常把兩者混在一起&lt;/td>
 &lt;td>在通訊與 runbook 明確區分控制面與資料面狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>區域依賴治理&lt;/td>
 &lt;td>單區域控制面異常可牽動多服務&lt;/td>
 &lt;td>把跨區備援與降載條件納入 release 與 incident gate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shared network health 訊號治理&lt;/td>
 &lt;td>內部網路異常訊號未被快速上提&lt;/td>
 &lt;td>補 shared infrastructure 指標到 [4.20 evidence package]&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident communication cadence&lt;/td>
 &lt;td>事故中更新節奏易受狀態不完整影響&lt;/td>
 &lt;td>固定 cadence，並保留「已知 / 未知 / 下一更新時間」欄位&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>觀測證據包： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package&lt;/a>&lt;/li>
&lt;li>可觀測性 operating model： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model&lt;/a>&lt;/li>
&lt;li>可靠性準備度： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 Reliability Readiness Review&lt;/a>&lt;/li>
&lt;li>止血與回復： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>影響評估： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/message/12721/">Summary of the AWS service event in the Northern Virginia (US-EAST-1) Region&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2021 年 AWS us-east-1 事件的核心教訓是：控制面退化不一定來自服務程式錯誤，內部網路壓力也能讓 API 與依賴鏈條同時失真。這類事故要先確認控制面健康，再決定是否進行服務層回退。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>AWS 在 2021-12-07 發生 us-east-1 多服務退化事件。官方資訊指出，內部網路裝置的異常行為導致這個區域的 API 請求與內部服務通訊壅塞，進而造成多個服務管理與控制面能力受影響。部分資料面能力可用，但控制面操作、狀態回報與恢復節奏出現延遲。</p>
<p>這類事故的難點在於，使用者看到的是「很多服務一起怪」，而工程上真正要先判斷的是：共同依賴是否先失真。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多服務 API 錯誤率同時上升</td>
          <td>共享控制面或內部網路層可能失真</td>
          <td>優先調查共用控制平面，不先分散逐服務排障</td>
      </tr>
      <tr>
          <td>控制操作延遲遠高於資料讀寫</td>
          <td>控制面與資料面可用性不同步</td>
          <td>對外通訊要分清 control/data plane 差異</td>
      </tr>
      <tr>
          <td>區域集中異常（us-east-1）</td>
          <td>區域依賴與路由聚集形成單點風險</td>
          <td>啟動跨區降載或備援策略</td>
      </tr>
      <tr>
          <td>狀態更新節奏出現抖動</td>
          <td>事故資訊供應鏈本身受影響</td>
          <td>建立固定 cadence 與替代更新通道</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>區域內部網路層出現異常與壅塞。</li>
<li>控制面 API 與內部依賴通訊受阻。</li>
<li>多服務管理能力與狀態回報受到影響。</li>
<li>部分服務資料面仍可運作，但操作與恢復節奏失真。</li>
<li>團隊逐步收斂網路壓力並恢復控制面可用性。</li>
</ol>
<p>這條路徑顯示：真正的擴散點在 shared internal network + control plane，不是某個單一服務程式。</p>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Control/Data plane 分離判讀</td>
          <td>對外敘述常把兩者混在一起</td>
          <td>在通訊與 runbook 明確區分控制面與資料面狀態</td>
      </tr>
      <tr>
          <td>區域依賴治理</td>
          <td>單區域控制面異常可牽動多服務</td>
          <td>把跨區備援與降載條件納入 release 與 incident gate</td>
      </tr>
      <tr>
          <td>Shared network health 訊號治理</td>
          <td>內部網路異常訊號未被快速上提</td>
          <td>補 shared infrastructure 指標到 [4.20 evidence package]</td>
      </tr>
      <tr>
          <td>Incident communication cadence</td>
          <td>事故中更新節奏易受狀態不完整影響</td>
          <td>固定 cadence，並保留「已知 / 未知 / 下一更新時間」欄位</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>觀測證據包： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
<li>可觀測性 operating model： <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a></li>
<li>可靠性準備度： <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 Reliability Readiness Review</a></li>
<li>止血與回復： <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 Containment / Recovery Strategy</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>影響評估： <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/message/12721/">Summary of the AWS service event in the Northern Virginia (US-EAST-1) Region</a></li>
</ul>
]]></content:encoded></item><item><title>Cloudflare 2023 Control Plane Token Incident</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/</guid><description>&lt;p>2023 年 Cloudflare control-plane 事故的核心教訓是：身份與憑證類變更一旦跨產品共用，單點錯誤會變成系統級連鎖故障。這類事故要先切的是信任邊界，不是先做流量微調。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cloudflare 在 2023-01-24 經歷 service token 相關變更問題，造成內外部控制面能力受影響，連帶影響多個產品面向。事件本質是控制面身份機制失效，並透過共用依賴擴散。&lt;/p>
&lt;p>這類事故的危險在於症狀看起來像多個服務同時不穩，但根因其實是同一個共享身份控制點。若沒有先識別 shared dependency，排障會被切成很多局部問題，恢復速度會顯著下降。&lt;/p>
&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>優先檢查 token / policy 最新變更&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗集中在控制面 API&lt;/td>
 &lt;td>問題偏向控制面，不是資料面容量瓶頸&lt;/td>
 &lt;td>啟動控制面優先處理，不先做業務層調參&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>局部回復但整體仍不穩&lt;/td>
 &lt;td>依賴鏈條有殘留錯誤狀態&lt;/td>
 &lt;td>補 dependency-by-dependency 驗證清單&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>ownership 與交接規則不足&lt;/td>
 &lt;td>指派 single incident owner 與決策記錄&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>控制面 token/身份相關變更進入生產環境。&lt;/li>
&lt;li>共享身份依賴開始出現授權或驗證失效。&lt;/li>
&lt;li>多個產品面的控制操作受阻，形成連鎖症狀。&lt;/li>
&lt;li>團隊透過回退與修正策略逐步收斂。&lt;/li>
&lt;li>事件後需回寫身份變更治理與事故交接流程。&lt;/li>
&lt;/ol>
&lt;p>這條路徑顯示：擴散關鍵在 shared identity dependency，不在單一產品流量高低。&lt;/p>
&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>token/policy 變更前缺少跨產品影響分析&lt;/td>
 &lt;td>補 shared dependency impact checklist&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>發布策略&lt;/td>
 &lt;td>身份控制面變更缺少逐層 rollout&lt;/td>
 &lt;td>先低風險範圍啟用，再逐步擴大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事故啟動條件&lt;/td>
 &lt;td>多產品異常時未即時指向 shared root&lt;/td>
 &lt;td>新增「多產品授權異常」的快速升級條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Decision log&lt;/td>
 &lt;td>假設、回退條件與責任分工不夠明確&lt;/td>
 &lt;td>事中強制記錄假設、證據、回退門檻與 owner&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence write-back&lt;/td>
 &lt;td>教訓停在事件敘述&lt;/td>
 &lt;td>回寫 &lt;code>07&lt;/code> 身分邊界治理、&lt;code>08&lt;/code> decision log、&lt;code>04&lt;/code> 控制面健康訊號&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Handoff protocol&lt;/td>
 &lt;td>長事故交接易遺失上下文&lt;/td>
 &lt;td>使用固定 handoff 模板，包含當前假設、已驗證路徑、未完成風險與下一步責任&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>身分邊界與權限治理： &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 Identity Access Boundary&lt;/a>&lt;/li>
&lt;li>規則推送安全閘門： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a>&lt;/li>
&lt;li>事故決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>控制面訊號治理： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/cloudflare-incident-on-january-24th-2023/">Cloudflare incident on January 24, 2023&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2023 年 Cloudflare control-plane 事故的核心教訓是：身份與憑證類變更一旦跨產品共用，單點錯誤會變成系統級連鎖故障。這類事故要先切的是信任邊界，不是先做流量微調。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Cloudflare 在 2023-01-24 經歷 service token 相關變更問題，造成內外部控制面能力受影響，連帶影響多個產品面向。事件本質是控制面身份機制失效，並透過共用依賴擴散。</p>
<p>這類事故的危險在於症狀看起來像多個服務同時不穩，但根因其實是同一個共享身份控制點。若沒有先識別 shared dependency，排障會被切成很多局部問題，恢復速度會顯著下降。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多產品同時出現驗證/授權異常</td>
          <td>共享身份或憑證控制點可能失效</td>
          <td>優先檢查 token / policy 最新變更</td>
      </tr>
      <tr>
          <td>失敗集中在控制面 API</td>
          <td>問題偏向控制面，不是資料面容量瓶頸</td>
          <td>啟動控制面優先處理，不先做業務層調參</td>
      </tr>
      <tr>
          <td>局部回復但整體仍不穩</td>
          <td>依賴鏈條有殘留錯誤狀態</td>
          <td>補 dependency-by-dependency 驗證清單</td>
      </tr>
      <tr>
          <td>回退後錯誤快速下降</td>
          <td>變更與故障關聯度高</td>
          <td>立即凍結同批身份變更與關聯部署</td>
      </tr>
      <tr>
          <td>事故中責任邊界模糊</td>
          <td>ownership 與交接規則不足</td>
          <td>指派 single incident owner 與決策記錄</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>控制面 token/身份相關變更進入生產環境。</li>
<li>共享身份依賴開始出現授權或驗證失效。</li>
<li>多個產品面的控制操作受阻，形成連鎖症狀。</li>
<li>團隊透過回退與修正策略逐步收斂。</li>
<li>事件後需回寫身份變更治理與事故交接流程。</li>
</ol>
<p>這條路徑顯示：擴散關鍵在 shared identity dependency，不在單一產品流量高低。</p>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>身份變更審核</td>
          <td>token/policy 變更前缺少跨產品影響分析</td>
          <td>補 shared dependency impact checklist</td>
      </tr>
      <tr>
          <td>發布策略</td>
          <td>身份控制面變更缺少逐層 rollout</td>
          <td>先低風險範圍啟用，再逐步擴大</td>
      </tr>
      <tr>
          <td>事故啟動條件</td>
          <td>多產品異常時未即時指向 shared root</td>
          <td>新增「多產品授權異常」的快速升級條件</td>
      </tr>
      <tr>
          <td>Decision log</td>
          <td>假設、回退條件與責任分工不夠明確</td>
          <td>事中強制記錄假設、證據、回退門檻與 owner</td>
      </tr>
      <tr>
          <td>Evidence write-back</td>
          <td>教訓停在事件敘述</td>
          <td>回寫 <code>07</code> 身分邊界治理、<code>08</code> decision log、<code>04</code> 控制面健康訊號</td>
      </tr>
      <tr>
          <td>Handoff protocol</td>
          <td>長事故交接易遺失上下文</td>
          <td>使用固定 handoff 模板，包含當前假設、已驗證路徑、未完成風險與下一步責任</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>身分邊界與權限治理： <a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 Identity Access Boundary</a></li>
<li>規則推送安全閘門： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a></li>
<li>事故決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>控制面訊號治理： <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/cloudflare-incident-on-january-24th-2023/">Cloudflare incident on January 24, 2023</a></li>
</ul>
]]></content:encoded></item><item><title>Gaming：高峰流量下的訊號新鮮度與 Cardinality</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/</guid><description>&lt;p>本案例的核心責任是避免高峰流量讓觀測系統本身失真。若訊號延遲與 cardinality 膨脹失控，值班決策會落在過期資料上。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一個線上多人遊戲平台，日活躍使用者約 50 萬人。每逢賽季開跑或限時活動，同時在線人數在 30 分鐘內從平日基線暴增 8-10 倍，matchmaking 服務的 request rate 從 5k/s 衝到 50k/s，遊戲伺服器同時運行的 match instance 從數千增到數萬。&lt;/p>
&lt;p>觀測系統在平日運作良好 — Prometheus 單機 scrape 500 萬 active series、Grafana dashboard 查詢秒級回應、告警在 1 分鐘內觸發。但每次活動開跑時，觀測系統本身開始劣化：dashboard 查詢從秒級變成分鐘級、告警延遲 5 分鐘以上才送到、部分 metric 直接消失。值班工程師在最需要觀測的時刻失去了可信訊號。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="cardinality-爆炸">Cardinality 爆炸&lt;/h3>
&lt;p>平日的 metric label 設計包含 &lt;code>match_id&lt;/code>、&lt;code>player_id&lt;/code> 跟 &lt;code>server_instance&lt;/code>。平日 active series 約 500 萬，活動開跑後 match 跟 player 數量暴增，active series 在 30 分鐘內衝到 2000 萬。Prometheus 的 head block 記憶體從 20 GB 暴增到 80 GB，超過機器 64 GB 上限，觸發 OOM kill。&lt;/p>
&lt;p>OOM 後 Prometheus 重啟需要 replay WAL，這段時間（5-15 分鐘）完全沒有 metric。活動最需要觀測的前 30 分鐘，觀測系統反而停擺。&lt;/p>
&lt;h3 id="scrape-freshness-延遲">Scrape freshness 延遲&lt;/h3>
&lt;p>即使 Prometheus 沒 OOM，大量 target 的 scrape 時間也會拉長。平日每輪 scrape 15 秒完成，活動期間拉長到 60-90 秒。Scrape interval 設定 30 秒時，下一輪 scrape 在上一輪還沒結束時就啟動，造成 sample 丟失跟時間錯位。Dashboard 上看到的數字可能延遲 2-3 分鐘，值班人員基於過期數據做判斷。&lt;/p>
&lt;h3 id="alert-閾值失真">Alert 閾值失真&lt;/h3>
&lt;p>告警規則基於平日 baseline 設定 — 例如 &lt;code>error_rate &amp;gt; 1%&lt;/code> 觸發。活動期間的 error rate 波動更大（matchmaking 短暫排隊造成的 timeout 增加是預期行為），平日閾值在活動期間持續觸發 false positive。值班人員開始 ignore alert，真正的問題（伺服器記憶體洩漏）被淹沒在噪音中。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="cardinality-guardrail">Cardinality guardrail&lt;/h3>
&lt;p>把高 cardinality label 從 real-time metric 移除。&lt;code>match_id&lt;/code> 和 &lt;code>player_id&lt;/code> 不再作為 Prometheus label，改為 log 和 trace 的欄位。Real-time metric 只保留 &lt;code>region&lt;/code>、&lt;code>server_pool&lt;/code>、&lt;code>game_mode&lt;/code> 等低 cardinality 維度。&lt;/p>
&lt;p>需要 per-match 或 per-player 分析時，走 log analytics pipeline（非 real-time，延遲 5-10 分鐘可接受）。這讓 Prometheus 的 active series 在活動期間從 2000 萬降到 800 萬，留在單機可承受範圍。&lt;/p>
&lt;h3 id="pre-aggregation-recording-rules">Pre-aggregation recording rules&lt;/h3>
&lt;p>為活動期間最常查的 pattern（per-region error rate、matchmaking queue depth、server utilization）建立 recording rules。Recording rules 在 Prometheus server 端預先計算，dashboard 查詢直接讀預計算結果，避免 heavy aggregation query 在活動期間拖慢 Prometheus。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是避免高峰流量讓觀測系統本身失真。若訊號延遲與 cardinality 膨脹失控，值班決策會落在過期資料上。</p>
<h2 id="業務背景">業務背景</h2>
<p>一個線上多人遊戲平台，日活躍使用者約 50 萬人。每逢賽季開跑或限時活動，同時在線人數在 30 分鐘內從平日基線暴增 8-10 倍，matchmaking 服務的 request rate 從 5k/s 衝到 50k/s，遊戲伺服器同時運行的 match instance 從數千增到數萬。</p>
<p>觀測系統在平日運作良好 — Prometheus 單機 scrape 500 萬 active series、Grafana dashboard 查詢秒級回應、告警在 1 分鐘內觸發。但每次活動開跑時，觀測系統本身開始劣化：dashboard 查詢從秒級變成分鐘級、告警延遲 5 分鐘以上才送到、部分 metric 直接消失。值班工程師在最需要觀測的時刻失去了可信訊號。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="cardinality-爆炸">Cardinality 爆炸</h3>
<p>平日的 metric label 設計包含 <code>match_id</code>、<code>player_id</code> 跟 <code>server_instance</code>。平日 active series 約 500 萬，活動開跑後 match 跟 player 數量暴增，active series 在 30 分鐘內衝到 2000 萬。Prometheus 的 head block 記憶體從 20 GB 暴增到 80 GB，超過機器 64 GB 上限，觸發 OOM kill。</p>
<p>OOM 後 Prometheus 重啟需要 replay WAL，這段時間（5-15 分鐘）完全沒有 metric。活動最需要觀測的前 30 分鐘，觀測系統反而停擺。</p>
<h3 id="scrape-freshness-延遲">Scrape freshness 延遲</h3>
<p>即使 Prometheus 沒 OOM，大量 target 的 scrape 時間也會拉長。平日每輪 scrape 15 秒完成，活動期間拉長到 60-90 秒。Scrape interval 設定 30 秒時，下一輪 scrape 在上一輪還沒結束時就啟動，造成 sample 丟失跟時間錯位。Dashboard 上看到的數字可能延遲 2-3 分鐘，值班人員基於過期數據做判斷。</p>
<h3 id="alert-閾值失真">Alert 閾值失真</h3>
<p>告警規則基於平日 baseline 設定 — 例如 <code>error_rate &gt; 1%</code> 觸發。活動期間的 error rate 波動更大（matchmaking 短暫排隊造成的 timeout 增加是預期行為），平日閾值在活動期間持續觸發 false positive。值班人員開始 ignore alert，真正的問題（伺服器記憶體洩漏）被淹沒在噪音中。</p>
<h2 id="解法">解法</h2>
<h3 id="cardinality-guardrail">Cardinality guardrail</h3>
<p>把高 cardinality label 從 real-time metric 移除。<code>match_id</code> 和 <code>player_id</code> 不再作為 Prometheus label，改為 log 和 trace 的欄位。Real-time metric 只保留 <code>region</code>、<code>server_pool</code>、<code>game_mode</code> 等低 cardinality 維度。</p>
<p>需要 per-match 或 per-player 分析時，走 log analytics pipeline（非 real-time，延遲 5-10 分鐘可接受）。這讓 Prometheus 的 active series 在活動期間從 2000 萬降到 800 萬，留在單機可承受範圍。</p>
<h3 id="pre-aggregation-recording-rules">Pre-aggregation recording rules</h3>
<p>為活動期間最常查的 pattern（per-region error rate、matchmaking queue depth、server utilization）建立 recording rules。Recording rules 在 Prometheus server 端預先計算，dashboard 查詢直接讀預計算結果，避免 heavy aggregation query 在活動期間拖慢 Prometheus。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># recording rule 示例</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">groups</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">peak_precompute</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">15s</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">      </span>- <span class="nt">record</span><span class="p">:</span><span class="w"> </span><span class="l">region:matchmaking_errors:rate5m</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">        </span><span class="nt">expr</span><span class="p">:</span><span class="w"> </span><span class="l">sum(rate(matchmaking_errors_total[5m])) by (region)</span></span></span></code></pre></div><h3 id="signal-tiering">Signal tiering</h3>
<p>把觀測訊號分成兩層：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>訊號類型</th>
          <th>Pipeline</th>
          <th>Freshness</th>
          <th>Cardinality 限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tier 1</td>
          <td>Golden signals（latency、error rate、throughput、saturation）</td>
          <td>Prometheus real-time</td>
          <td>&lt; 30s</td>
          <td>嚴格（低 cardinality label only）</td>
      </tr>
      <tr>
          <td>Tier 2</td>
          <td>Debug signals（per-match、per-player、per-request）</td>
          <td>Log + trace analytics</td>
          <td>5-10 min</td>
          <td>無限制</td>
      </tr>
  </tbody>
</table>
<p>Tier 1 支撐告警跟即時 dashboard，保證活動期間不劣化。Tier 2 支撐事後分析跟 root cause investigation，接受延遲。</p>
<h3 id="dynamic-alert-threshold">Dynamic alert threshold</h3>
<p>活動期間啟用「高峰模式」alert profile — 調高 error rate 閾值（1% → 5%）、加長 <code>for:</code> duration（1m → 5m）、停用已知在活動期間會 false positive 的告警。高峰模式由活動排程系統自動觸發，活動結束後自動切回平日 profile。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>高 cardinality real-time</th>
          <th>分層治理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Debug 即時性</td>
          <td>高（per-match real-time）</td>
          <td>低到中（per-match 延遲 5-10 min）</td>
      </tr>
      <tr>
          <td>Prometheus 穩定性</td>
          <td>低（活動期間 OOM 風險）</td>
          <td>高（active series 可控）</td>
      </tr>
      <tr>
          <td>Dashboard 回應速度</td>
          <td>活動期間劣化</td>
          <td>穩定（recording rules 預計算）</td>
      </tr>
      <tr>
          <td>告警可信度</td>
          <td>低（false positive 淹沒真問題）</td>
          <td>中到高（dynamic threshold 降噪）</td>
      </tr>
      <tr>
          <td>維護複雜度</td>
          <td>低（一套 pipeline）</td>
          <td>中（兩套 pipeline + 高峰模式切換）</td>
      </tr>
  </tbody>
</table>
<p>分層治理的核心取捨是犧牲 per-match real-time debug 能力，換取觀測系統在高峰期間的穩定。這個取捨在活動場景成立 — 活動期間最需要的是「整體是否健康」的判斷，per-match debug 在事後分析夠用。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality Cost Governance</a>：cardinality guardrail 的設計原則與偵測機制。</li>
<li><a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a>：scrape freshness、sampling bias 與 signal tiering。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：real-time vs batch analytics pipeline 的分層設計。</li>
<li><a href="/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 Dashboard Alert</a>：dynamic alert threshold 與高峰模式切換。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>流量高峰期間 Prometheus 記憶體使用異常增長或觸發 OOM</li>
<li>Dashboard 在尖峰時段查詢變慢或 timeout，正好是最需要看的時候</li>
<li>Alert 在活動期間大量觸發但多數是 false positive，值班人員開始 ignore</li>
<li><code>prometheus_tsdb_head_series</code> 在特定時段突然暴增，結束後回落</li>
<li>Metric label 中包含高 cardinality identifier（user_id、session_id、request_id）</li>
</ul>
]]></content:encoded></item><item><title>Gaming：高峰流量與隔離邊界選型</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cases/gaming-peak-traffic-and-isolation/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cases/gaming-peak-traffic-and-isolation/</guid><description>&lt;p>這個案例的核心責任是把活動高峰轉成預先可驗證的容量與隔離決策。Gaming 場景的失效通常來自瞬間峰值與連線風暴疊加。&lt;/p>
&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>peak burst ratio&lt;/td>
 &lt;td>尖峰是否超過模型緩衝&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>matchmaking queue lag&lt;/td>
 &lt;td>非同步鏈路是否壅塞&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/async-delivery-selection/" data-link-title="0.3 非同步與事件傳遞選型" data-link-desc="區分背景工作、durable queue、stream、pub/sub 與 outbox 的選型邊界">0.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>reconnect storm indicator&lt;/td>
 &lt;td>回復是否放大負載&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="風險與邊界">風險與邊界&lt;/h2>
&lt;p>只追求低延遲而忽略隔離邊界，會在高峰時把單一熱點擴散成全域事故。選型時需要同時定義分流邏輯與分批恢復策略。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>把容量假設回寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>，並在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14&lt;/a> 補多事故協調規則。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是把活動高峰轉成預先可驗證的容量與隔離決策。Gaming 場景的失效通常來自瞬間峰值與連線風暴疊加。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>peak burst ratio</td>
          <td>尖峰是否超過模型緩衝</td>
          <td><a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5</a></td>
      </tr>
      <tr>
          <td>matchmaking queue lag</td>
          <td>非同步鏈路是否壅塞</td>
          <td><a href="/blog/backend/00-service-selection/async-delivery-selection/" data-link-title="0.3 非同步與事件傳遞選型" data-link-desc="區分背景工作、durable queue、stream、pub/sub 與 outbox 的選型邊界">0.3</a></td>
      </tr>
      <tr>
          <td>reconnect storm indicator</td>
          <td>回復是否放大負載</td>
          <td><a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a></td>
      </tr>
  </tbody>
</table>
<h2 id="風險與邊界">風險與邊界</h2>
<p>只追求低延遲而忽略隔離邊界，會在高峰時把單一熱點擴散成全域事故。選型時需要同時定義分流邏輯與分批恢復策略。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>把容量假設回寫 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a>，並在 <a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a> 補多事故協調規則。</p>
]]></content:encoded></item><item><title>Cloudflare</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/</guid><description>&lt;p>Cloudflare 是 anycast edge 的代表、單一配置 push 即可影響全球流量、是 configuration push 風險 / regex catastrophic backtracking / BGP 信任的教學標竿。Cloudflare 工程部落格公開度極高、post-mortem 細節豐富。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>全球 configuration push 的 blast radius：為何 60 秒內可癱瘓全球流量&lt;/li>
&lt;li>Regex CPU 耗盡：catastrophic backtracking 如何繞過所有 timeout&lt;/li>
&lt;li>BGP 風險：路由洩漏如何把流量吸入錯誤 ASN&lt;/li>
&lt;li>Recovery 設計：為何 configuration rollback 需要 dataplane 層協作&lt;/li>
&lt;/ul>
&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>2019&lt;/td>
 &lt;td>Regex CPU 27 分鐘&lt;/td>
 &lt;td>catastrophic backtracking、WAF rule 部署流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2020&lt;/td>
 &lt;td>BGP route leak&lt;/td>
 &lt;td>跨 ASN 信任、網路層事故止血&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2022&lt;/td>
 &lt;td>配置 push 全球退化&lt;/td>
 &lt;td>變更節奏、staged rollout 的價值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2023&lt;/td>
 &lt;td>Control plane token incident&lt;/td>
 &lt;td>身分控制面與多產品連鎖影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2026&lt;/td>
 &lt;td>BYOIP / BGP withdrawal&lt;/td>
 &lt;td>Addressing API、prefix withdrawal、狀態恢復&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/" data-link-title="Cloudflare 2023 Workers KV Deployment Tool Misconfiguration" data-link-desc="2023-10-30 Cloudflare 控制面事故：deployment tool 設定錯誤造成 Workers KV 連鎖影響，重點在變更範圍限制與決策回寫。">2023 Workers KV Deployment Tool Misconfiguration&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/" data-link-title="Cloudflare 2023 Workers KV Deployment Tool Misconfiguration" data-link-desc="2023-10-30 Cloudflare 控制面事故：deployment tool 設定錯誤造成 Workers KV 連鎖影響，重點在變更範圍限制與決策回寫。">2023 Workers KV Deployment Tool Misconfiguration&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Cloudflare 這個案例在講的是 edge 平台如何把一個小錯誤快速放大到全球。讀者先看懂配置推送、runtime 驗證與路由撤銷各自的責任，再把 anycast 與 control plane 當成事故擴散的核心路徑。&lt;/p></description><content:encoded><![CDATA[<p>Cloudflare 是 anycast edge 的代表、單一配置 push 即可影響全球流量、是 configuration push 風險 / regex catastrophic backtracking / BGP 信任的教學標竿。Cloudflare 工程部落格公開度極高、post-mortem 細節豐富。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>全球 configuration push 的 blast radius：為何 60 秒內可癱瘓全球流量</li>
<li>Regex CPU 耗盡：catastrophic backtracking 如何繞過所有 timeout</li>
<li>BGP 風險：路由洩漏如何把流量吸入錯誤 ASN</li>
<li>Recovery 設計：為何 configuration rollback 需要 dataplane 層協作</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2019</td>
          <td>Regex CPU 27 分鐘</td>
          <td>catastrophic backtracking、WAF rule 部署流程</td>
      </tr>
      <tr>
          <td>2020</td>
          <td>BGP route leak</td>
          <td>跨 ASN 信任、網路層事故止血</td>
      </tr>
      <tr>
          <td>2022</td>
          <td>配置 push 全球退化</td>
          <td>變更節奏、staged rollout 的價值</td>
      </tr>
      <tr>
          <td>2023</td>
          <td>Control plane token incident</td>
          <td>身分控制面與多產品連鎖影響</td>
      </tr>
      <tr>
          <td>2026</td>
          <td>BYOIP / BGP withdrawal</td>
          <td>Addressing API、prefix withdrawal、狀態恢復</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/" data-link-title="Cloudflare 2023 Workers KV Deployment Tool Misconfiguration" data-link-desc="2023-10-30 Cloudflare 控制面事故：deployment tool 設定錯誤造成 Workers KV 連鎖影響，重點在變更範圍限制與決策回寫。">2023 Workers KV Deployment Tool Misconfiguration</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/" data-link-title="Cloudflare 2023 Workers KV Deployment Tool Misconfiguration" data-link-desc="2023-10-30 Cloudflare 控制面事故：deployment tool 設定錯誤造成 Workers KV 連鎖影響，重點在變更範圍限制與決策回寫。">2023 Workers KV Deployment Tool Misconfiguration</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>Cloudflare 這個案例在講的是 edge 平台如何把一個小錯誤快速放大到全球。讀者先看懂配置推送、runtime 驗證與路由撤銷各自的責任，再把 anycast 與 control plane 當成事故擴散的核心路徑。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 regex、workers 設定或 deployment tool 出現問題時，真正危險的是錯誤被快速推到全網，單一節點故障反而容易收斂。當 BGP 或 BYOIP 參數變動時，回滾與驗證就必須先於擴散，否則影響會直接表現在全球流量上。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否在全網推送前做足夠的配置驗證</li>
<li>能否把 blast radius 限制在局部 edge 群組</li>
<li>能否在 CPU 熱點或路由撤銷前先看見異常</li>
<li>能否把 rollback 動作設計成快速且可驗證</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Cloudflare 和 Fastly 都在講 edge 平台的快速擴散，但 Cloudflare 更常暴露控制面與部署工具的問題。它和 AWS S3、GCP 放在一起看，可以更清楚看到全球網路事故是配置與路由鏈條的連鎖反應，單一節點失效很少是起因。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2019 年 regex CPU outage 是 catastrophic backtracking 直接拖垮 edge runtime 的經典樣本。</li>
<li>2023 年控制面事故與 2026 年 BYOIP / BGP 事故則顯示配置與路由都能成為全球擴散點。</li>
<li>這組樣本也能對照配置推送與回滾速度對 blast radius 的影響。</li>
<li>Cloudflare 的事故史很適合拿來和 Fastly 比較 edge 平台差異。</li>
<li>workers / deployment tool misconfiguration 讓控制面本身成為風險。</li>
<li>anycast edge 讓路由錯誤能在全球尺度迅速顯現。</li>
<li>global propagation 讓回滾時間直接影響用戶體感。</li>
<li>control plane bug 常常比 data plane bug 更難局部化。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019">Details of the Cloudflare outage on July 2, 2019</a>：regex CPU / catastrophic backtracking 事故的官方回顧。</li>
<li><a href="https://blog.cloudflare.com/cloudflare-incident-on-january-24th-2023/">Cloudflare incident on January 24, 2023</a>：service token / control plane 變更導致的多產品連鎖影響。</li>
<li><a href="https://blog.cloudflare.com/cloudflare-incident-on-october-30-2023/">Cloudflare incident on October 30, 2023</a>：Workers KV / deployment tool misconfiguration 的控制面事故。</li>
<li><a href="https://blog.cloudflare.com/cloudflare-outage-february-20-2026/">Cloudflare outage on February 20, 2026</a>：BYOIP / BGP 變更造成的路由撤銷事故。</li>
</ul>
]]></content:encoded></item><item><title>Netflix</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/</guid><description>&lt;p>Netflix 是 Chaos Engineering 的起源、Chaos Monkey 跟 Simian Army 是領域標準工具的概念來源、FIT（Failure Injection Testing）是大規模 production chaos 的實作範本。教學重點在「故障注入如何作為 first-class 工程實踐」。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Chaos Monkey 起點：在 production 隨機殺實例為何能改進架構&lt;/li>
&lt;li>Simian Army 工具鏈：Latency / Janitor / Conformity 等不同維度的 chaos&lt;/li>
&lt;li>FIT：把 chaos 從 instance 層升級到 request 層、攻擊更精細&lt;/li>
&lt;li>Chaos Maturity Model：團隊採用 chaos 的能力分級&lt;/li>
&lt;li>Steady state hypothesis：chaos 實驗的科學方法基礎&lt;/li>
&lt;/ul>
&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>Chaos Monkey&lt;/td>
 &lt;td>起源、規則、為何在 weekday business hour&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Simian Army&lt;/td>
 &lt;td>多維度故障注入的設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>FIT&lt;/td>
 &lt;td>Request-level fault injection 的工程化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Chaos Engineering Manifesto&lt;/td>
 &lt;td>hypothesis / scope / blast radius 控制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production chaos vs Staging&lt;/td>
 &lt;td>為何 production 才有真實價值&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">N1&lt;/a>&lt;/td>
 &lt;td>Steady State、Chaos 與 FIT&lt;/td>
 &lt;td>把故障注入變成可證偽、可停止、可回寫的驗證流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">N2&lt;/a>&lt;/td>
 &lt;td>Business-Hours Guardrails&lt;/td>
 &lt;td>把時段策略、風險邊界與應變能力整合進 chaos 驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">N3&lt;/a>&lt;/td>
 &lt;td>FIT 證據交接&lt;/td>
 &lt;td>把故障注入結果轉成 release gate 可用證據&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Netflix 這個案例在講的是故障注入如何從實驗變成工程制度。讀者要先分辨 steady state、hypothesis、blast radius 與回復條件各自扮演的角色，才能理解為什麼 chaos 是驗證服務韌性的方法，演示層面的價值是次要的。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當團隊只在 staging 做演練時，先看測試是否真的碰到生產流量的分布與依賴關係。當問題需要更細的干預時，再往 FIT 這種 request-level fault injection 移動，讓故障落在真正會被客戶碰到的路徑上。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否先寫出 steady state，再設計實驗&lt;/li>
&lt;li>能否說清楚 blast radius 與 rollback 條件&lt;/li>
&lt;li>能否說明為何在 business hour 做 chaos 反而更安全&lt;/li>
&lt;li>能否判斷問題需要 instance-level 還是 request-level 注入&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Netflix 把「先驗證再承擔風險」這件事做成制度，和 AWS S3、Cloudflare 這類事故頁形成對照。前者是在可控條件下主動打破假設，後者是在失敗後回頭整理假設，因此兩者一起讀才能看懂 reliability 與 incident response 的分工。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>Chaos Monkey 直接驗證實例被殺掉後，服務是否仍能維持 steady state。&lt;/li>
&lt;li>FIT 把故障注入從 instance 級推進到 request 級，讓實驗更貼近真實流量路徑。&lt;/li>
&lt;li>Simian Army 讓不同故障類型有各自的注入面。&lt;/li>
&lt;li>business-hour chaos 讓測試更接近真實營運節奏。&lt;/li>
&lt;li>chaos maturity model 讓團隊知道自己在採用故障注入的哪個階段。&lt;/li>
&lt;li>steady state hypothesis 讓實驗成為可證偽的工程判斷，超越單純演示。&lt;/li>
&lt;li>latency monkey 讓延遲問題成為可以主動驗證的故障型態。&lt;/li>
&lt;li>janitor / conformity 類工具把環境清理與架構規則也納入韌性管理。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey&lt;/a>：Chaos Monkey 的現行開源實作。&lt;/li>
&lt;li>&lt;a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey">Netflix/SimianArmy Wiki: Chaos Monkey&lt;/a>：Simian Army 舊版 wiki，說明 business-hours chaos 的基本規則。&lt;/li>
&lt;li>&lt;a href="https://github.com/Netflix/SimianArmy">Netflix/SimianArmy&lt;/a>：Simian Army 套件入口，補齊多種 monkey 的整體脈絡。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Netflix 是 Chaos Engineering 的起源、Chaos Monkey 跟 Simian Army 是領域標準工具的概念來源、FIT（Failure Injection Testing）是大規模 production chaos 的實作範本。教學重點在「故障注入如何作為 first-class 工程實踐」。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Chaos Monkey 起點：在 production 隨機殺實例為何能改進架構</li>
<li>Simian Army 工具鏈：Latency / Janitor / Conformity 等不同維度的 chaos</li>
<li>FIT：把 chaos 從 instance 層升級到 request 層、攻擊更精細</li>
<li>Chaos Maturity Model：團隊採用 chaos 的能力分級</li>
<li>Steady state hypothesis：chaos 實驗的科學方法基礎</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chaos Monkey</td>
          <td>起源、規則、為何在 weekday business hour</td>
      </tr>
      <tr>
          <td>Simian Army</td>
          <td>多維度故障注入的設計</td>
      </tr>
      <tr>
          <td>FIT</td>
          <td>Request-level fault injection 的工程化</td>
      </tr>
      <tr>
          <td>Chaos Engineering Manifesto</td>
          <td>hypothesis / scope / blast radius 控制</td>
      </tr>
      <tr>
          <td>Production chaos vs Staging</td>
          <td>為何 production 才有真實價值</td>
      </tr>
  </tbody>
</table>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">N1</a></td>
          <td>Steady State、Chaos 與 FIT</td>
          <td>把故障注入變成可證偽、可停止、可回寫的驗證流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">N2</a></td>
          <td>Business-Hours Guardrails</td>
          <td>把時段策略、風險邊界與應變能力整合進 chaos 驗證</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">N3</a></td>
          <td>FIT 證據交接</td>
          <td>把故障注入結果轉成 release gate 可用證據</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Netflix 這個案例在講的是故障注入如何從實驗變成工程制度。讀者要先分辨 steady state、hypothesis、blast radius 與回復條件各自扮演的角色，才能理解為什麼 chaos 是驗證服務韌性的方法，演示層面的價值是次要的。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當團隊只在 staging 做演練時，先看測試是否真的碰到生產流量的分布與依賴關係。當問題需要更細的干預時，再往 FIT 這種 request-level fault injection 移動，讓故障落在真正會被客戶碰到的路徑上。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否先寫出 steady state，再設計實驗</li>
<li>能否說清楚 blast radius 與 rollback 條件</li>
<li>能否說明為何在 business hour 做 chaos 反而更安全</li>
<li>能否判斷問題需要 instance-level 還是 request-level 注入</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Netflix 把「先驗證再承擔風險」這件事做成制度，和 AWS S3、Cloudflare 這類事故頁形成對照。前者是在可控條件下主動打破假設，後者是在失敗後回頭整理假設，因此兩者一起讀才能看懂 reliability 與 incident response 的分工。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>Chaos Monkey 直接驗證實例被殺掉後，服務是否仍能維持 steady state。</li>
<li>FIT 把故障注入從 instance 級推進到 request 級，讓實驗更貼近真實流量路徑。</li>
<li>Simian Army 讓不同故障類型有各自的注入面。</li>
<li>business-hour chaos 讓測試更接近真實營運節奏。</li>
<li>chaos maturity model 讓團隊知道自己在採用故障注入的哪個階段。</li>
<li>steady state hypothesis 讓實驗成為可證偽的工程判斷，超越單純演示。</li>
<li>latency monkey 讓延遲問題成為可以主動驗證的故障型態。</li>
<li>janitor / conformity 類工具把環境清理與架構規則也納入韌性管理。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey</a>：Chaos Monkey 的現行開源實作。</li>
<li><a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey">Netflix/SimianArmy Wiki: Chaos Monkey</a>：Simian Army 舊版 wiki，說明 business-hours chaos 的基本規則。</li>
<li><a href="https://github.com/Netflix/SimianArmy">Netflix/SimianArmy</a>：Simian Army 套件入口，補齊多種 monkey 的整體脈絡。</li>
</ul>
]]></content:encoded></item><item><title>8.2 PayPal：支付平台與 NoSQL / build pipelines</title><link>https://tarrragon.github.io/blog/go/08-case-studies/paypal/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/paypal/</guid><description>&lt;p>PayPal 的案例很適合拿來理解 Go 在複雜企業系統中的角色。官方案例提到，他們的 NoSQL 與 DB proxy 原本在多執行緒模式下非常複雜，而 Go 的 channels 與 goroutines 幫助團隊把這些條件收斂成更清楚的結構。之後，PayPal 也把 build、test、release pipelines 建在 Go 上。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/paypal">PayPal Taps Go to Modernize and Scale&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 不只適合對外服務，也適合內部工程平台。&lt;/li>
&lt;li>當系統條件變多時，明確並發模型比隱式 thread 管理更容易維護。&lt;/li>
&lt;li>Go 的價值常常在於讓大系統更容易演進。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/paypal/paypal-rest-api-specifications">paypal/paypal-rest-api-specifications&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/paypal">paypal/github organization&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>PayPal 的 Go 內部系統細節不會完整公開，但它的公開 API spec 與 SDK 生態，能幫你理解大型支付平台如何維持清楚的外部合約。&lt;/p></description><content:encoded><![CDATA[<p>PayPal 的案例很適合拿來理解 Go 在複雜企業系統中的角色。官方案例提到，他們的 NoSQL 與 DB proxy 原本在多執行緒模式下非常複雜，而 Go 的 channels 與 goroutines 幫助團隊把這些條件收斂成更清楚的結構。之後，PayPal 也把 build、test、release pipelines 建在 Go 上。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/paypal">PayPal Taps Go to Modernize and Scale</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 不只適合對外服務，也適合內部工程平台。</li>
<li>當系統條件變多時，明確並發模型比隱式 thread 管理更容易維護。</li>
<li>Go 的價值常常在於讓大系統更容易演進。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/paypal/paypal-rest-api-specifications">paypal/paypal-rest-api-specifications</a></li>
<li><a href="https://github.com/paypal">paypal/github organization</a></li>
</ul>
<p>PayPal 的 Go 內部系統細節不會完整公開，但它的公開 API spec 與 SDK 生態，能幫你理解大型支付平台如何維持清楚的外部合約。</p>
]]></content:encoded></item><item><title>案例：Rust 正則表達式</title><link>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/rust-regex/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/rust-regex/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Rust 的 regex crate 加速模式匹配。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">模組六：用 Rust 擴展 Python&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/" data-link-title="案例：PyO3 文字解析" data-link-desc="用 PyO3 和 Rust 實現高效能的 Markdown 連結解析器">6.1 PyO3 文字解析&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 使用 Python 的 re 模組進行多種模式匹配驗證：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Pattern definitions for various validation checks&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">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&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;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_LOGGING_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">CONFIG_LOADER_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">GIT_UTILS_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;write_hook_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_pretooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_posttooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">BAD_OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;print\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="n">VALID_NAME_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9](/python-advanced/06-rust-extensions/case-studies/rust-regex/[a-z0-9\-_]*[a-z0-9])?\.py$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_has_import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check if content matches any of the import patterns&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_matches_pattern&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check if content matches any pattern&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate file naming convention&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="n">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="n">valid_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VALID_NAME_PATTERNS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... validation logic&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式碼展示了幾個核心問題：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>重複編譯&lt;/strong>：每次呼叫 &lt;code>re.search()&lt;/code> 或 &lt;code>re.match()&lt;/code> 都可能重新編譯正則表達式&lt;/li>
&lt;li>&lt;strong>多模式匹配&lt;/strong>：需要遍歷多個模式逐一檢查&lt;/li>
&lt;li>&lt;strong>混合使用場景&lt;/strong>：部分用於 &lt;code>match&lt;/code>（從頭匹配），部分用於 &lt;code>search&lt;/code>（任意位置）&lt;/li>
&lt;/ol>
&lt;h3 id="效能限制">效能限制&lt;/h3>
&lt;p>Python re 模組的限制：&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>&lt;strong>回溯型引擎&lt;/strong>&lt;/td>
 &lt;td>NFA with backtracking&lt;/td>
 &lt;td>某些模式可能導致指數級時間複雜度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>解釋器開銷&lt;/strong>&lt;/td>
 &lt;td>每次匹配都經過 Python 呼叫&lt;/td>
 &lt;td>大量匹配時累積顯著延遲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>無硬體加速&lt;/strong>&lt;/td>
 &lt;td>純軟體實作&lt;/td>
 &lt;td>無法利用 SIMD 等現代 CPU 特性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>GIL 限制&lt;/strong>&lt;/td>
 &lt;td>受 Global Interpreter Lock 影響&lt;/td>
 &lt;td>多執行緒場景效能受限&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h4 id="病態輸入示例">病態輸入示例&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Pathological pattern: catastrophic backtracking&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">pattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;(a+)+b&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;a&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">25&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s2">&amp;#34;c&amp;#34;&lt;/span> &lt;span class="c1"># No match, triggers backtracking&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&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">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text&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">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Python re: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">elapsed&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># May take several seconds!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>用 Rust regex crate 取代 Python re&lt;/li>
&lt;li>利用 Rust regex 的 DFA 引擎確保線性時間複雜度&lt;/li>
&lt;li>使用 &lt;code>RegexSet&lt;/code> 實現高效批次驗證&lt;/li>
&lt;li>預編譯正則表達式，避免重複編譯開銷&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立專案結構">步驟 1：建立專案結構&lt;/h4>





&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">&lt;span class="c1"># Create new maturin project&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">maturin new hook_validator_rs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> hook_validator_rs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># Project structure&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">hook_validator_rs/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── Cargo.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── pyproject.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">└── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> └── lib.rs&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>編輯 &lt;code>Cargo.toml&lt;/code>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Rust 的 regex crate 加速模式匹配。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">模組六：用 Rust 擴展 Python</a></li>
<li><a href="/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/" data-link-title="案例：PyO3 文字解析" data-link-desc="用 PyO3 和 Rust 實現高效能的 Markdown 連結解析器">6.1 PyO3 文字解析</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 使用 Python 的 re 模組進行多種模式匹配驗證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Pattern definitions for various validation checks</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/06-rust-extensions/case-studies/rust-regex/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if content matches any of the import patterns&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if content matches any pattern&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate file naming convention&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="n">valid_name</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERNS</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="c1"># ... validation logic</span></span></span></code></pre></div><p>這段程式碼展示了幾個核心問題：</p>
<ol>
<li><strong>重複編譯</strong>：每次呼叫 <code>re.search()</code> 或 <code>re.match()</code> 都可能重新編譯正則表達式</li>
<li><strong>多模式匹配</strong>：需要遍歷多個模式逐一檢查</li>
<li><strong>混合使用場景</strong>：部分用於 <code>match</code>（從頭匹配），部分用於 <code>search</code>（任意位置）</li>
</ol>
<h3 id="效能限制">效能限制</h3>
<p>Python re 模組的限制：</p>
<table>
  <thead>
      <tr>
          <th>限制</th>
          <th>說明</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>回溯型引擎</strong></td>
          <td>NFA with backtracking</td>
          <td>某些模式可能導致指數級時間複雜度</td>
      </tr>
      <tr>
          <td><strong>解釋器開銷</strong></td>
          <td>每次匹配都經過 Python 呼叫</td>
          <td>大量匹配時累積顯著延遲</td>
      </tr>
      <tr>
          <td><strong>無硬體加速</strong></td>
          <td>純軟體實作</td>
          <td>無法利用 SIMD 等現代 CPU 特性</td>
      </tr>
      <tr>
          <td><strong>GIL 限制</strong></td>
          <td>受 Global Interpreter Lock 影響</td>
          <td>多執行緒場景效能受限</td>
      </tr>
  </tbody>
</table>
<h4 id="病態輸入示例">病態輸入示例</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Pathological pattern: catastrophic backtracking</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;(a+)+b&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">25</span> <span class="o">+</span> <span class="s2">&#34;c&#34;</span>  <span class="c1"># No match, triggers backtracking</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Python re: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>  <span class="c1"># May take several seconds!</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li>用 Rust regex crate 取代 Python re</li>
<li>利用 Rust regex 的 DFA 引擎確保線性時間複雜度</li>
<li>使用 <code>RegexSet</code> 實現高效批次驗證</li>
<li>預編譯正則表達式，避免重複編譯開銷</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立專案結構">步驟 1：建立專案結構</h4>





<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"><span class="c1"># Create new maturin project</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">maturin new hook_validator_rs
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nb">cd</span> hook_validator_rs
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Project structure</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">hook_validator_rs/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── Cargo.toml
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">└── src/
</span></span><span class="line"><span class="ln">10</span><span class="cl">    └── lib.rs</span></span></code></pre></div><p>編輯 <code>Cargo.toml</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">package</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;hook_validator_rs&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.1.0&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">edition</span> <span class="p">=</span> <span class="s2">&#34;2021&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">lib</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;hook_validator_rs&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">crate-type</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;cdylib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">pyo3</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;0.22&#34;</span><span class="p">,</span> <span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;extension-module&#34;</span><span class="p">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">regex</span> <span class="p">=</span> <span class="s2">&#34;1.10&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">once_cell</span> <span class="p">=</span> <span class="s2">&#34;1.19&#34;</span></span></span></code></pre></div><h4 id="步驟-2定義預編譯正則表達式">步驟 2：定義預編譯正則表達式</h4>
<p>使用 <code>once_cell::sync::Lazy</code> 實現執行緒安全的延遲初始化：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="p">{</span><span class="n">Regex</span><span class="p">,</span><span class="w"> </span><span class="n">RegexSet</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="c1">// Pre-compiled individual patterns
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">HOOK_IO_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">HOOK_LOGGING_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">CONFIG_LOADER_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">GIT_UTILS_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">OUTPUT_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">BAD_OUTPUT_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;print\s*\(\s*json\.dumps\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w"></span><span class="c1">// For filename validation (anchored match)
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1"></span><span class="k">static</span><span class="w"> </span><span class="no">VALID_NAME_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;^[a-z0-9](/python-advanced/06-rust-extensions/case-studies/rust-regex/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid regex pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="w"></span><span class="p">});</span></span></span></code></pre></div><h5 id="為什麼用-once_cellsynclazy">為什麼用 <code>once_cell::sync::Lazy</code>？</h5>
<ul>
<li><strong>執行緒安全</strong>：<code>Lazy</code> 確保初始化只執行一次，即使多執行緒同時存取</li>
<li><strong>延遲初始化</strong>：只在第一次使用時編譯正則表達式</li>
<li><strong>零執行時開銷</strong>：初始化後的存取是零成本的</li>
</ul>
<h4 id="步驟-3實作批次匹配邏輯">步驟 3：實作批次匹配邏輯</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="sd">/// Result of validating import patterns in source code
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_hook_io</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_hook_logging</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_config_loader</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_git_utils</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_good_output</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_bad_output</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">            </span><span class="s">&#34;ImportCheckResult(hook_io=</span><span class="si">{}</span><span class="s">, logging=</span><span class="si">{}</span><span class="s">, config=</span><span class="si">{}</span><span class="s">, git=</span><span class="si">{}</span><span class="s">, good_out=</span><span class="si">{}</span><span class="s">, bad_out=</span><span class="si">{}</span><span class="s">)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_hook_io</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">has_hook_logging</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_config_loader</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">has_git_utils</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_good_output</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">has_bad_output</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">        </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w"></span><span class="sd">/// Check all import patterns in a single pass through the content
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">check_imports</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">    </span><span class="n">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">        </span><span class="n">has_hook_io</span>: <span class="nc">HOOK_IO_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">        </span><span class="n">has_hook_logging</span>: <span class="nc">HOOK_LOGGING_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">        </span><span class="n">has_config_loader</span>: <span class="nc">CONFIG_LOADER_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">        </span><span class="n">has_git_utils</span>: <span class="nc">GIT_UTILS_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">        </span><span class="n">has_good_output</span>: <span class="nc">OUTPUT_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">        </span><span class="n">has_bad_output</span>: <span class="nc">BAD_OUTPUT_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w"></span><span class="sd">/// Validate filename against naming convention
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">is_valid_hook_name</span><span class="p">(</span><span class="n">filename</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">    </span><span class="no">VALID_NAME_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w"></span><span class="sd">/// Check which specific patterns matched (for detailed reporting)
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get_matched_patterns</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w"> </span><span class="n">pattern_group</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">regex_set</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="n">pattern_group</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_io&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">HOOK_IO_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_logging&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">HOOK_LOGGING_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="w">        </span><span class="s">&#34;config_loader&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">CONFIG_LOADER_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="w">        </span><span class="s">&#34;git_utils&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">GIT_UTILS_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="w">        </span><span class="s">&#34;output&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">OUTPUT_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="w">        </span><span class="s">&#34;bad_output&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;*</span><span class="no">BAD_OUTPUT_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="w">        </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[],</span><span class="w">
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="w">    </span><span class="n">regex_set</span><span class="p">.</span><span class="n">matches</span><span class="p">(</span><span class="n">content</span><span class="p">).</span><span class="n">iter</span><span class="p">().</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h4 id="步驟-4進階批次驗證-api">步驟 4：進階批次驗證 API</h4>
<p>對於需要一次驗證大量檔案的場景，提供更高效的批次 API：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="sd">/// Batch validation result for multiple files
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">results</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">valid_names</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;BatchValidationResult(</span><span class="si">{}</span><span class="s"> files)&#34;</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="p">.</span><span class="n">len</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">    </span><span class="sd">/// Get files that are missing hook_io import
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">files_missing_hook_io</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="o">!</span><span class="n">r</span><span class="p">.</span><span class="n">has_hook_io</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">    </span><span class="sd">/// Get files with bad output patterns
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">files_with_bad_output</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">has_bad_output</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w"></span><span class="sd">/// Validate multiple files in batch
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="sd">/// This is more efficient than calling check_imports for each file
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="sd">/// because it can potentially parallelize the work.
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">validate_batch</span><span class="p">(</span><span class="n">files</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="nb">String</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">results</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">content</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">valid_names</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">keys</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">path</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">rsplit</span><span class="p">(</span><span class="sc">&#39;/&#39;</span><span class="p">).</span><span class="n">next</span><span class="p">().</span><span class="n">unwrap_or</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="w">            </span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">is_valid_hook_name</span><span class="p">(</span><span class="n">filename</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w">    </span><span class="n">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">results</span><span class="p">,</span><span class="w"> </span><span class="n">valid_names</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h4 id="步驟-5pyo3-模組導出">步驟 5：PyO3 模組導出</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="sd">/// Rust-powered hook validator with pre-compiled regex patterns
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="sd"></span><span class="cp">#[pymodule]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">hook_validator_rs</span><span class="p">(</span><span class="n">m</span>: <span class="kp">&amp;</span><span class="nc">Bound</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="p">,</span><span class="w"> </span><span class="n">PyModule</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">check_imports</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">is_valid_hook_name</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">get_matched_patterns</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">validate_batch</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">BatchValidationResult</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h4 id="步驟-6python-端整合">步驟 6：Python 端整合</h4>
<p>在 Python 端無縫整合 Rust 模組：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Hook 合規性驗證工具（Rust 加速版）
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">This module provides a drop-in replacement for the pure Python
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">hook_validator, using Rust regex crate for pattern matching.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># Try to import Rust extension, fall back to pure Python</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="kn">import</span> <span class="nn">hook_validator_rs</span> <span class="k">as</span> <span class="nn">_rs</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">_USE_RUST</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">_USE_RUST</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Warning: Rust extension not available, using pure Python&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook compliance validator with optional Rust acceleration&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check shared module imports using Rust regex&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">if</span> <span class="n">_USE_RUST</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="c1"># Use Rust-accelerated pattern matching</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">_rs</span><span class="o">.</span><span class="n">check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">has_hook_io</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_io import read_hook_input, write_hook_output&#34;</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">has_hook_logging</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_logging import (recommended)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_logging import setup_hook_logging&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">has_bad_output</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using print(json.dumps(...)) instead of write_hook_output()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Replace with: write_hook_output(output_dict)&#34;</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="c1"># Fallback to pure Python regex</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_imports_python</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate filename against naming convention&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="k">if</span> <span class="n">_USE_RUST</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="n">valid</span> <span class="o">=</span> <span class="n">_rs</span><span class="o">.</span><span class="n">is_valid_hook_name</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="n">valid</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/06-rust-extensions/case-studies/rust-regex/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">                <span class="n">filename</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Invalid filename: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake-case or kebab-case: check_permissions.py&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate all hooks with batch optimization&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="n">hooks_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="n">hook_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">hooks_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">if</span> <span class="n">_USE_RUST</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">            <span class="c1"># Use batch validation for multiple files</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">            <span class="n">files_content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">                <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">):</span> <span class="n">f</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">                <span class="k">if</span> <span class="ow">not</span> <span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">            <span class="n">batch_result</span> <span class="o">=</span> <span class="n">_rs</span><span class="o">.</span><span class="n">validate_batch</span><span class="p">(</span><span class="n">files_content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">            <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">content</span> <span class="ow">in</span> <span class="n">files_content</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">                <span class="n">import_result</span> <span class="o">=</span> <span class="n">batch_result</span><span class="o">.</span><span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">                <span class="n">valid_name</span> <span class="o">=</span> <span class="n">batch_result</span><span class="o">.</span><span class="n">valid_names</span><span class="p">[</span><span class="n">path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_import_result_to_issues</span><span class="p">(</span><span class="n">import_result</span><span class="p">,</span> <span class="n">valid_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">
</span></span><span class="line"><span class="ln">160</span><span class="cl">            <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="c1"># Single file or no Rust: use standard validation</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">                <span class="k">if</span> <span class="ow">not</span> <span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="k">def</span> <span class="nf">_import_result_to_issues</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">        <span class="n">result</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="n">valid_name</span><span class="p">:</span> <span class="nb">bool</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert Rust ImportCheckResult to list of issues&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">valid_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Invalid filename format&#34;</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">has_hook_io</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">has_bad_output</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using deprecated output pattern&#34;</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_imports_python</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Pure Python fallback for import checking&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="n">hook_io_patterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">            <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">hook_io_patterns</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<p>以下是完整的 <code>src/lib.rs</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">  1</span><span class="cl"><span class="sd">//! Hook Validator - Rust regex acceleration for Python hook validation
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="sd">//!
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="sd">//! This module provides pre-compiled regex patterns for validating
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="sd">//! Claude Code hook files, with significant performance improvements
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="sd">//! over pure Python regex.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="sd"></span><span class="w">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="p">{</span><span class="n">Regex</span><span class="p">,</span><span class="w"> </span><span class="n">RegexSet</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1">// Pre-compiled Regex Patterns
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="w"></span><span class="sd">/// Import patterns for hook_io module
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">HOOK_IO_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid HOOK_IO_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="w"></span><span class="sd">/// Import patterns for hook_logging module
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">HOOK_LOGGING_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid HOOK_LOGGING_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="w"></span><span class="sd">/// Import patterns for config_loader module
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">CONFIG_LOADER_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid CONFIG_LOADER_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="w"></span><span class="sd">/// Import patterns for git_utils module
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">GIT_UTILS_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid GIT_UTILS_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="w"></span><span class="sd">/// Recommended output function patterns
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">OUTPUT_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid OUTPUT_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="w"></span><span class="sd">/// Deprecated output patterns (should be avoided)
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">BAD_OUTPUT_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;print\s*\(\s*json\.dumps\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid BAD_OUTPUT_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="w"></span><span class="sd">/// Valid hook filename pattern (anchored)
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">VALID_NAME_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;^[a-z0-9](/python-advanced/06-rust-extensions/case-studies/rust-regex/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid VALID_NAME_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="w"></span><span class="sd">/// JSON output detection patterns
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="sd"></span><span class="k">static</span><span class="w"> </span><span class="no">JSON_OUTPUT_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;json\.dumps&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;write_hook_output&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="w">        </span><span class="sa">r</span><span class="s">&#34;create_.*_output&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="w">    </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid JSON_OUTPUT_REGEX pattern&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="c1">// Result Types
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="w"></span><span class="sd">/// Result of checking import patterns in source code
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone, Debug)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_hook_io</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_hook_logging</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_config_loader</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_git_utils</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_good_output</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_bad_output</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">has_json_output</span>: <span class="kt">bool</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="w">            </span><span class="s">&#34;ImportCheckResult(hook_io=</span><span class="si">{}</span><span class="s">, logging=</span><span class="si">{}</span><span class="s">, config=</span><span class="si">{}</span><span class="s">, git=</span><span class="si">{}</span><span class="s">, </span><span class="se">\</span><span class="s">
</span></span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="s">             good_out=</span><span class="si">{}</span><span class="s">, bad_out=</span><span class="si">{}</span><span class="s">, json_out=</span><span class="si">{}</span><span class="s">)&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_hook_io</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_hook_logging</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_config_loader</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_git_utils</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_good_output</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_bad_output</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">has_json_output</span><span class="w">
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="w">        </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="w">    </span><span class="sd">/// Check if the hook uses recommended output patterns
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">uses_recommended_output</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">has_good_output</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">has_bad_output</span><span class="w">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="w">    </span><span class="sd">/// Check if the hook has all required imports
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">has_required_imports</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">has_hook_io</span><span class="w">
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="w"></span><span class="sd">/// Batch validation result for multiple files
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="sd"></span><span class="cp">#[pyclass]</span><span class="w">
</span></span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="w"></span><span class="cp">#[derive(Clone, Debug)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">results</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="w">    </span><span class="cp">#[pyo3(get)]</span><span class="w">
</span></span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="n">valid_names</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="o">&gt;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="w"></span><span class="cp">#[pymethods]</span><span class="w">
</span></span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="w"></span><span class="k">impl</span><span class="w"> </span><span class="n">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">__repr__</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">String</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="w">        </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;BatchValidationResult(</span><span class="si">{}</span><span class="s"> files)&#34;</span><span class="p">,</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="p">.</span><span class="n">len</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="w">    </span><span class="sd">/// Get list of files missing hook_io import
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">files_missing_hook_io</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="w">
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="o">!</span><span class="n">r</span><span class="p">.</span><span class="n">has_hook_io</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="w">    </span><span class="sd">/// Get list of files using bad output patterns
</span></span></span><span class="line"><span class="ln">164</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">files_with_bad_output</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">165</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="w">
</span></span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">167</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">r</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">has_bad_output</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">168</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">169</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">170</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="w">    </span><span class="sd">/// Get list of files with invalid names
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">files_with_invalid_names</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="n">valid_names</span><span class="w">
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">valid</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="o">!*</span><span class="n">valid</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="w">            </span><span class="p">.</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="w">    </span><span class="sd">/// Get summary statistics
</span></span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="sd"></span><span class="w">    </span><span class="k">fn</span> <span class="nf">summary</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">stats</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashMap</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="s">&#34;total&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">results</span><span class="p">.</span><span class="n">len</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="w">            </span><span class="s">&#34;missing_hook_io&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">files_missing_hook_io</span><span class="p">().</span><span class="n">len</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="w">        </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">190</span><span class="cl"><span class="w">            </span><span class="s">&#34;bad_output&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">files_with_bad_output</span><span class="p">().</span><span class="n">len</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="w">        </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="w">            </span><span class="s">&#34;invalid_names&#34;</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="w">            </span><span class="bp">self</span><span class="p">.</span><span class="n">files_with_invalid_names</span><span class="p">().</span><span class="n">len</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="ln">196</span><span class="cl"><span class="w">        </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="w">
</span></span></span><span class="line"><span class="ln">198</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">199</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">200</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">201</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">202</span><span class="cl"><span class="c1">// Public API Functions
</span></span></span><span class="line"><span class="ln">203</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">204</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">205</span><span class="cl"><span class="w"></span><span class="sd">/// Check all import patterns in source code
</span></span></span><span class="line"><span class="ln">206</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="sd">/// This function performs all pattern checks in a single pass through
</span></span></span><span class="line"><span class="ln">208</span><span class="cl"><span class="sd">/// the content, making it much more efficient than individual checks.
</span></span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">210</span><span class="cl"><span class="sd">/// # Arguments
</span></span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="sd">/// * `content` - The source code content to check
</span></span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="sd">/// # Returns
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="sd">/// * `ImportCheckResult` - Results of all pattern checks
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">check_imports</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="w">    </span><span class="n">ImportCheckResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="w">        </span><span class="n">has_hook_io</span>: <span class="nc">HOOK_IO_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">219</span><span class="cl"><span class="w">        </span><span class="n">has_hook_logging</span>: <span class="nc">HOOK_LOGGING_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">220</span><span class="cl"><span class="w">        </span><span class="n">has_config_loader</span>: <span class="nc">CONFIG_LOADER_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">221</span><span class="cl"><span class="w">        </span><span class="n">has_git_utils</span>: <span class="nc">GIT_UTILS_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">222</span><span class="cl"><span class="w">        </span><span class="n">has_good_output</span>: <span class="nc">OUTPUT_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">223</span><span class="cl"><span class="w">        </span><span class="n">has_bad_output</span>: <span class="nc">BAD_OUTPUT_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">224</span><span class="cl"><span class="w">        </span><span class="n">has_json_output</span>: <span class="nc">JSON_OUTPUT_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">225</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">226</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">227</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">228</span><span class="cl"><span class="w"></span><span class="sd">/// Validate filename against naming convention
</span></span></span><span class="line"><span class="ln">229</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="sd">/// Valid names must:
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="sd">/// - Start and end with lowercase alphanumeric
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="sd">/// - Contain only lowercase letters, numbers, hyphens, underscores
</span></span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="sd">/// - Have .py extension
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="sd">/// # Arguments
</span></span></span><span class="line"><span class="ln">236</span><span class="cl"><span class="sd">/// * `filename` - The filename to validate (just the name, not full path)
</span></span></span><span class="line"><span class="ln">237</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">238</span><span class="cl"><span class="sd">/// # Returns
</span></span></span><span class="line"><span class="ln">239</span><span class="cl"><span class="sd">/// * `bool` - True if the filename is valid
</span></span></span><span class="line"><span class="ln">240</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">241</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">is_valid_hook_name</span><span class="p">(</span><span class="n">filename</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">242</span><span class="cl"><span class="w">    </span><span class="no">VALID_NAME_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">243</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="w"></span><span class="sd">/// Get indices of matched patterns in a pattern group
</span></span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">247</span><span class="cl"><span class="sd">/// Useful for detailed reporting of which specific patterns matched.
</span></span></span><span class="line"><span class="ln">248</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">249</span><span class="cl"><span class="sd">/// # Arguments
</span></span></span><span class="line"><span class="ln">250</span><span class="cl"><span class="sd">/// * `content` - The source code content to check
</span></span></span><span class="line"><span class="ln">251</span><span class="cl"><span class="sd">/// * `pattern_group` - One of: &#34;hook_io&#34;, &#34;hook_logging&#34;, &#34;config_loader&#34;,
</span></span></span><span class="line"><span class="ln">252</span><span class="cl"><span class="sd">///                     &#34;git_utils&#34;, &#34;output&#34;, &#34;bad_output&#34;, &#34;json_output&#34;
</span></span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">254</span><span class="cl"><span class="sd">/// # Returns
</span></span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="sd">/// * `Vec&lt;usize&gt;` - Indices of patterns that matched
</span></span></span><span class="line"><span class="ln">256</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">257</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">get_matched_patterns</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w"> </span><span class="n">pattern_group</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">258</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">regex_set</span>: <span class="kp">&amp;</span><span class="nc">RegexSet</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">match</span><span class="w"> </span><span class="n">pattern_group</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">259</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_io&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">HOOK_IO_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">260</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_logging&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">HOOK_LOGGING_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">261</span><span class="cl"><span class="w">        </span><span class="s">&#34;config_loader&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">CONFIG_LOADER_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">262</span><span class="cl"><span class="w">        </span><span class="s">&#34;git_utils&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">GIT_UTILS_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">263</span><span class="cl"><span class="w">        </span><span class="s">&#34;output&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">OUTPUT_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">264</span><span class="cl"><span class="w">        </span><span class="s">&#34;bad_output&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">BAD_OUTPUT_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">265</span><span class="cl"><span class="w">        </span><span class="s">&#34;json_output&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="o">&amp;</span><span class="no">JSON_OUTPUT_REGEX</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">266</span><span class="cl"><span class="w">        </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[],</span><span class="w">
</span></span></span><span class="line"><span class="ln">267</span><span class="cl"><span class="w">    </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="ln">268</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">269</span><span class="cl"><span class="w">    </span><span class="n">regex_set</span><span class="p">.</span><span class="n">matches</span><span class="p">(</span><span class="n">content</span><span class="p">).</span><span class="n">iter</span><span class="p">().</span><span class="n">collect</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">270</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">271</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">272</span><span class="cl"><span class="w"></span><span class="sd">/// Validate multiple files in a single batch operation
</span></span></span><span class="line"><span class="ln">273</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">274</span><span class="cl"><span class="sd">/// This is significantly more efficient than validating files one by one,
</span></span></span><span class="line"><span class="ln">275</span><span class="cl"><span class="sd">/// especially when dealing with many files.
</span></span></span><span class="line"><span class="ln">276</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">277</span><span class="cl"><span class="sd">/// # Arguments
</span></span></span><span class="line"><span class="ln">278</span><span class="cl"><span class="sd">/// * `files` - HashMap of file paths to their contents
</span></span></span><span class="line"><span class="ln">279</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">280</span><span class="cl"><span class="sd">/// # Returns
</span></span></span><span class="line"><span class="ln">281</span><span class="cl"><span class="sd">/// * `BatchValidationResult` - Combined results for all files
</span></span></span><span class="line"><span class="ln">282</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">283</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">validate_batch</span><span class="p">(</span><span class="n">files</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="nb">String</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">results</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="n">content</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">)))</span><span class="w">
</span></span></span><span class="line"><span class="ln">287</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">valid_names</span>: <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">files</span><span class="w">
</span></span></span><span class="line"><span class="ln">290</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">keys</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="ln">291</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">path</span><span class="o">|</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="w">            </span><span class="c1">// Extract filename from path
</span></span></span><span class="line"><span class="ln">293</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">path</span><span class="p">.</span><span class="n">rsplit</span><span class="p">(</span><span class="sc">&#39;/&#39;</span><span class="p">).</span><span class="n">next</span><span class="p">().</span><span class="n">unwrap_or</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">294</span><span class="cl"><span class="w">            </span><span class="p">(</span><span class="n">path</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">is_valid_hook_name</span><span class="p">(</span><span class="n">filename</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="w">    </span><span class="n">BatchValidationResult</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">299</span><span class="cl"><span class="w">        </span><span class="n">results</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">300</span><span class="cl"><span class="w">        </span><span class="n">valid_names</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">302</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">303</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">304</span><span class="cl"><span class="w"></span><span class="sd">/// Check if content contains specific import pattern (simple check)
</span></span></span><span class="line"><span class="ln">305</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">306</span><span class="cl"><span class="sd">/// # Arguments
</span></span></span><span class="line"><span class="ln">307</span><span class="cl"><span class="sd">/// * `content` - The source code to check
</span></span></span><span class="line"><span class="ln">308</span><span class="cl"><span class="sd">/// * `module_name` - The module to check for: &#34;hook_io&#34;, &#34;hook_logging&#34;, etc.
</span></span></span><span class="line"><span class="ln">309</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="sd">/// # Returns
</span></span></span><span class="line"><span class="ln">311</span><span class="cl"><span class="sd">/// * `bool` - True if the import pattern is found
</span></span></span><span class="line"><span class="ln">312</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">313</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">has_import</span><span class="p">(</span><span class="n">content</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w"> </span><span class="n">module_name</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">314</span><span class="cl"><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">module_name</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">315</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_io&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="no">HOOK_IO_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">316</span><span class="cl"><span class="w">        </span><span class="s">&#34;hook_logging&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="no">HOOK_LOGGING_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">317</span><span class="cl"><span class="w">        </span><span class="s">&#34;config_loader&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="no">CONFIG_LOADER_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">318</span><span class="cl"><span class="w">        </span><span class="s">&#34;git_utils&#34;</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="no">GIT_UTILS_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">content</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">319</span><span class="cl"><span class="w">        </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">320</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">321</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">322</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">323</span><span class="cl"><span class="w"></span><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">324</span><span class="cl"><span class="c1">// Python Module Definition
</span></span></span><span class="line"><span class="ln">325</span><span class="cl"><span class="c1">// ============================================================================
</span></span></span><span class="line"><span class="ln">326</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">327</span><span class="cl"><span class="w"></span><span class="sd">/// Rust-accelerated hook validator with pre-compiled regex patterns
</span></span></span><span class="line"><span class="ln">328</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">329</span><span class="cl"><span class="sd">/// This module provides significant performance improvements over pure Python
</span></span></span><span class="line"><span class="ln">330</span><span class="cl"><span class="sd">/// regex for validating Claude Code hook files. Key features:
</span></span></span><span class="line"><span class="ln">331</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln">332</span><span class="cl"><span class="sd">/// - Pre-compiled regex patterns using once_cell
</span></span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="sd">/// - RegexSet for efficient multi-pattern matching
</span></span></span><span class="line"><span class="ln">334</span><span class="cl"><span class="sd">/// - Batch validation API for multiple files
</span></span></span><span class="line"><span class="ln">335</span><span class="cl"><span class="sd">/// - Guaranteed linear time complexity (DFA engine)
</span></span></span><span class="line"><span class="ln">336</span><span class="cl"><span class="sd"></span><span class="cp">#[pymodule]</span><span class="w">
</span></span></span><span class="line"><span class="ln">337</span><span class="cl"><span class="w"></span><span class="k">fn</span> <span class="nf">hook_validator_rs</span><span class="p">(</span><span class="n">m</span>: <span class="kp">&amp;</span><span class="nc">Bound</span><span class="o">&lt;</span><span class="nb">&#39;_</span><span class="p">,</span><span class="w"> </span><span class="n">PyModule</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">338</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">check_imports</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">339</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">is_valid_hook_name</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">340</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">get_matched_patterns</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">341</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">validate_batch</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">342</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_function</span><span class="p">(</span><span class="fm">wrap_pyfunction!</span><span class="p">(</span><span class="n">has_import</span><span class="p">,</span><span class="w"> </span><span class="n">m</span><span class="p">)</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">343</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">ImportCheckResult</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">344</span><span class="cl"><span class="w">    </span><span class="n">m</span><span class="p">.</span><span class="n">add_class</span>::<span class="o">&lt;</span><span class="n">BatchValidationResult</span><span class="o">&gt;</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">345</span><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span><span class="line"><span class="ln">346</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h3 id="建置與測試">建置與測試</h3>





<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"><span class="c1"># Build the extension</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">maturin develop --release
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Run tests</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">python -c <span class="s2">&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">import hook_validator_rs as rs
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2"># Test basic import checking
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">content = &#39;&#39;&#39;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">from hook_logging import setup_hook_logging
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">&#39;&#39;&#39;
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">result = rs.check_imports(content)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">print(f&#39;Import check: {result}&#39;)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">print(f&#39;Has hook_io: {result.has_hook_io}&#39;)
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">print(f&#39;Uses recommended output: {result.uses_recommended_output()}&#39;)
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2"># Test filename validation
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">print(f&#39;Valid name \&#34;check-permissions.py\&#34;: {rs.is_valid_hook_name(\&#34;check-permissions.py\&#34;)}&#39;)
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">print(f&#39;Valid name \&#34;BadName.py\&#34;: {rs.is_valid_hook_name(\&#34;BadName.py\&#34;)}&#39;)
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">&#34;</span></span></span></code></pre></div><h3 id="效能比較">效能比較</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;Performance comparison: Python re vs Rust regex&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</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="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Run benchmark and return average time in microseconds&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">        <span class="n">func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">avg_us</span> <span class="o">=</span> <span class="p">(</span><span class="n">elapsed</span> <span class="o">/</span> <span class="n">iterations</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">avg_us</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us/iteration (</span><span class="si">{</span><span class="n">iterations</span><span class="si">}</span><span class="s2"> iterations)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">return</span> <span class="n">avg_us</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="c1"># Test content (typical hook file)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="n">TEST_CONTENT</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="s1">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s1">&#34;&#34;&#34;Example hook for testing performance&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s1">import json
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s1">import sys
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s1">from pathlib import Path
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s1">from hook_logging import setup_hook_logging
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s1">from config_loader import load_config
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s1">    logger = setup_hook_logging(&#34;example-hook&#34;)
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s1">    hook_input = read_hook_input()
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s1">    # Process input
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s1">    result = {&#34;decision&#34;: &#34;approve&#34;}
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s1">    write_hook_output(result)
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="c1"># Python patterns</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="n">HOOK_IO_PATTERNS_PY</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="n">HOOK_LOGGING_PATTERNS_PY</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="k">def</span> <span class="nf">python_check</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Pure Python regex check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">has_hook_io</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">HOOK_IO_PATTERNS_PY</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">has_logging</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">HOOK_LOGGING_PATTERNS_PY</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="k">return</span> <span class="n">has_hook_io</span><span class="p">,</span> <span class="n">has_logging</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="k">def</span> <span class="nf">python_check_compiled</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Python regex with pre-compiled patterns&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="k">global</span> <span class="n">_compiled_hook_io</span><span class="p">,</span> <span class="n">_compiled_logging</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">has_hook_io</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">_compiled_hook_io</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">has_logging</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">_compiled_logging</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="k">return</span> <span class="n">has_hook_io</span><span class="p">,</span> <span class="n">has_logging</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="c1"># Pre-compile Python patterns</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="n">_compiled_hook_io</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">HOOK_IO_PATTERNS_PY</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="n">_compiled_logging</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">HOOK_LOGGING_PATTERNS_PY</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="k">def</span> <span class="nf">rust_check</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Rust regex check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="kn">import</span> <span class="nn">hook_validator_rs</span> <span class="k">as</span> <span class="nn">rs</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">rs</span><span class="o">.</span><span class="n">check_imports</span><span class="p">(</span><span class="n">TEST_CONTENT</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">has_hook_io</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">has_hook_logging</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Performance Comparison: Python re vs Rust regex&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Content size: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">TEST_CONTENT</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="c1"># Warm up</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="n">python_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="n">python_check_compiled</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="n">rust_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="n">py_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="s2">&#34;Python re (uncompiled)&#34;</span><span class="p">,</span> <span class="n">python_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="n">py_compiled_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="s2">&#34;Python re (compiled)&#34;</span><span class="p">,</span> <span class="n">python_check_compiled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="n">rust_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="s2">&#34;Rust regex&#34;</span><span class="p">,</span> <span class="n">rust_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Results Summary&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Python uncompiled: </span><span class="si">{</span><span class="n">py_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Python compiled:   </span><span class="si">{</span><span class="n">py_compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Rust regex:        </span><span class="si">{</span><span class="n">rust_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Speedup vs uncompiled: </span><span class="si">{</span><span class="n">py_time</span> <span class="o">/</span> <span class="n">rust_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Speedup vs compiled:   </span><span class="si">{</span><span class="n">py_compiled_time</span> <span class="o">/</span> <span class="n">rust_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><p>典型結果：</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">============================================================
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Performance Comparison: Python re vs Rust regex
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Content size: 512 bytes
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Python re (uncompiled): 12.45 us/iteration (10000 iterations)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">Python re (compiled):    4.32 us/iteration (10000 iterations)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Rust regex:              0.89 us/iteration (10000 iterations)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">11</span><span class="cl">Results Summary
</span></span><span class="line"><span class="ln">12</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">13</span><span class="cl">Python uncompiled: 12.45 us
</span></span><span class="line"><span class="ln">14</span><span class="cl">Python compiled:    4.32 us
</span></span><span class="line"><span class="ln">15</span><span class="cl">Rust regex:         0.89 us
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">Speedup vs uncompiled: 14.0x
</span></span><span class="line"><span class="ln">18</span><span class="cl">Speedup vs compiled:   4.9x</span></span></code></pre></div><h4 id="病態輸入效能比較">病態輸入效能比較</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;Pathological input benchmark - demonstrating DFA vs backtracking&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">test_catastrophic_backtracking</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Test pattern that causes catastrophic backtracking in NFA engines
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Pattern: (a+)+b
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Input: &#34;aaa...a&#34; (no &#39;b&#39; at end)
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Python re: O(2^n) time complexity
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Rust regex: O(n) time complexity (DFA engine)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;(a+)+b&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Catastrophic Backtracking Test&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Pattern: (a+)+b&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">15</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">25</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="n">n</span> <span class="o">+</span> <span class="s2">&#34;c&#34;</span>  <span class="c1"># No match - triggers backtracking</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># Python test</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">except</span> <span class="ne">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">py_time</span> <span class="o">=</span> <span class="s2">&#34;&gt;5s (timeout)&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">py_time</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span><span class="o">*</span><span class="mi">1000</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">ms&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="c1"># Note: Rust regex doesn&#39;t support backreferences,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="c1"># so (a+)+b is rewritten as a+b internally</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="c1"># This demonstrates why Rust regex is safe from this attack</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;n=</span><span class="si">{</span><span class="n">n</span><span class="si">:</span><span class="s2">2d</span><span class="si">}</span><span class="s2">: Python=</span><span class="si">{</span><span class="n">py_time</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">test_regex_dos</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Test ReDoS (Regular Expression Denial of Service) patterns
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="c1"># Common ReDoS patterns</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">redos_patterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s2">&#34;(a+)+$&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">20</span> <span class="o">+</span> <span class="s2">&#34;!&#34;</span><span class="p">),</span>          <span class="c1"># Nested quantifiers</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s2">&#34;(a|aa)+$&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">20</span> <span class="o">+</span> <span class="s2">&#34;!&#34;</span><span class="p">),</span>        <span class="c1"># Overlapping alternatives</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s2">&#34;(.*a)</span><span class="si">{10}</span><span class="s2">$&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">10</span> <span class="o">+</span> <span class="s2">&#34;!&#34;</span><span class="p">),</span>      <span class="c1"># Repeated wildcards</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">ReDoS Pattern Tests&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">text</span> <span class="ow">in</span> <span class="n">redos_patterns</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">:</span><span class="s2">20s</span><span class="si">}</span><span class="s2"> Time: </span><span class="si">{</span><span class="n">elapsed</span><span class="o">*</span><span class="mi">1000</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">ms&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="n">test_catastrophic_backtracking</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="n">test_regex_dos</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Note: Rust regex crate uses DFA/hybrid engine&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;that guarantees O(n) time complexity for all inputs.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;It does NOT support backreferences, which prevents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;catastrophic backtracking by design.&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Python re</th>
          <th>Rust regex</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>引擎類型</strong></td>
          <td>NFA with backtracking</td>
          <td>DFA/混合引擎</td>
      </tr>
      <tr>
          <td><strong>時間複雜度</strong></td>
          <td>最壞 O(2^n)</td>
          <td>保證 O(n)</td>
      </tr>
      <tr>
          <td><strong>功能完整性</strong></td>
          <td>完整（lookahead、backreference）</td>
          <td>部分限制（無 backreference）</td>
      </tr>
      <tr>
          <td><strong>整合難度</strong></td>
          <td>無（內建）</td>
          <td>需要 FFI（PyO3 + Maturin）</td>
      </tr>
      <tr>
          <td><strong>除錯便利</strong></td>
          <td>Python 原生</td>
          <td>需要 Rust 工具鏈</td>
      </tr>
      <tr>
          <td><strong>記憶體安全</strong></td>
          <td>GC 管理</td>
          <td>編譯時保證</td>
      </tr>
      <tr>
          <td><strong>多執行緒</strong></td>
          <td>受 GIL 限制</td>
          <td>完全平行化</td>
      </tr>
      <tr>
          <td><strong>SIMD 加速</strong></td>
          <td>無</td>
          <td>自動啟用</td>
      </tr>
  </tbody>
</table>
<h3 id="rust-regex-不支援的功能">Rust regex 不支援的功能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// These patterns will fail to compile in Rust regex:
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="c1">// 1. Backreferences
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// r&#34;(\w+)\s+\1&#34;  // ERROR: backreference not supported
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="c1">// 2. Lookahead/Lookbehind
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// r&#34;(?=foo)&#34;    // ERROR: lookahead not supported
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// r&#34;(?&lt;=foo)&#34;   // ERROR: lookbehind not supported
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="c1">// 3. Atomic groups
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// r&#34;(?&gt;foo)&#34;    // ERROR: atomic groups not supported
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"></span><span class="c1">// Workaround: Use regex-fancy crate for these features
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// (with performance trade-offs)
</span></span></span></code></pre></div><h2 id="什麼時候該用-rust-regex">什麼時候該用 Rust regex？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>大量文字需要驗證</strong>：日誌分析、程式碼審查、批次處理</li>
<li><strong>正則表達式可能有病態輸入</strong>：用戶提供的輸入、不可信來源</li>
<li><strong>需要保證線性時間</strong>：安全性要求、SLA 保證</li>
<li><strong>高併發場景</strong>：多執行緒處理、Web 服務</li>
<li><strong>效能關鍵路徑</strong>：CI/CD pipeline、即時驗證</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>需要 lookahead/lookbehind</strong>：複雜的文字邊界檢查</li>
<li><strong>需要 backreference</strong>：重複單詞檢測、HTML 標籤匹配</li>
<li><strong>驗證次數很少</strong>：一次性腳本、開發階段</li>
<li><strong>模式簡單</strong>：固定字串、簡單前綴/後綴檢查</li>
<li><strong>團隊不熟悉 Rust</strong>：維護成本可能超過效能收益</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="1-基礎練習email-驗證">1. 基礎練習：Email 驗證</h3>
<p>用 Rust regex 實作 email 地址驗證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Exercise: Implement email validation
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="n">Regex</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">EMAIL_REGEX</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">Regex</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="c1">// TODO: Implement email pattern
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// Requirements:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// - Local part: alphanumeric + dots + underscores + hyphens
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// - @ symbol
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// - Domain: alphanumeric + dots + hyphens
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// - TLD: 2-6 alphabetic characters
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="sa">r</span><span class="s">&#34;TODO&#34;</span><span class="p">).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid email regex&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">is_valid_email</span><span class="p">(</span><span class="n">email</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">bool</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="no">EMAIL_REGEX</span><span class="p">.</span><span class="n">is_match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w"></span><span class="c1">// Test cases:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">// is_valid_email(&#34;user@example.com&#34;) -&gt; true
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">// is_valid_email(&#34;user.name+tag@example.co.uk&#34;) -&gt; true
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1">// is_valid_email(&#34;invalid@&#34;) -&gt; false
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">// is_valid_email(&#34;@example.com&#34;) -&gt; false
</span></span></span></code></pre></div><h3 id="2-進階練習regexset-批次匹配">2. 進階練習：RegexSet 批次匹配</h3>
<p>實作一個程式語言檢測器，判斷程式碼片段是哪種語言：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Exercise: Language detection using RegexSet
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">once_cell</span>::<span class="n">sync</span>::<span class="n">Lazy</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">regex</span>::<span class="n">RegexSet</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">LANGUAGE_PATTERNS</span>: <span class="nc">Lazy</span><span class="o">&lt;</span><span class="n">RegexSet</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Lazy</span>::<span class="n">new</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="n">RegexSet</span>::<span class="n">new</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">        </span><span class="c1">// TODO: Add patterns for different languages
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// 0: Python (def, import, from ... import)
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// 1: JavaScript (const, let, =&gt;)
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// 2: Rust (fn, let mut, impl)
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span><span class="w">        </span><span class="c1">// 3: Go (func, package, import)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="p">]).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;Invalid language patterns&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w"></span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w"></span><span class="k">static</span><span class="w"> </span><span class="no">LANGUAGE_NAMES</span>: <span class="p">[</span><span class="o">&amp;</span><span class="kt">str</span><span class="p">;</span><span class="w"> </span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">&#34;Python&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;JavaScript&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Rust&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Go&#34;</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">detect_languages</span><span class="p">(</span><span class="n">code</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">    </span><span class="c1">// TODO: Return list of detected languages
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// Hint: Use LANGUAGE_PATTERNS.matches(code)
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="fm">vec!</span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w"></span><span class="c1">// Test case:
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1">// detect_languages(&#34;def hello():\n    print(&#39;Hi&#39;)&#34;) -&gt; [&#34;Python&#34;]
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1">// detect_languages(&#34;const x = () =&gt; {}&#34;) -&gt; [&#34;JavaScript&#34;]
</span></span></span></code></pre></div><h3 id="3-挑戰題病態輸入防護">3. 挑戰題：病態輸入防護</h3>
<p>設計一個安全的正則表達式驗證器，拒絕可能導致 ReDoS 的模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Challenge: ReDoS-safe regex validator
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">use</span><span class="w"> </span><span class="n">pyo3</span>::<span class="n">prelude</span>::<span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="sd">/// Validate that a regex pattern is safe from ReDoS attacks
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd">///
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd">/// Unsafe patterns to detect:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd">/// 1. Nested quantifiers: (a+)+
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sd">/// 2. Overlapping alternatives: (a|a)+
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sd">/// 3. Long quantified groups with wildcards: (.*)+
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">is_safe_pattern</span><span class="p">(</span><span class="n">pattern</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="c1">// Strategy 1: Try to compile with Rust regex
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// Rust regex rejects inherently unsafe patterns
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="k">match</span><span class="w"> </span><span class="n">regex</span>::<span class="n">Regex</span>::<span class="n">new</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="nb">Ok</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="nb">Ok</span><span class="p">(</span><span class="kc">true</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">        </span><span class="nb">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">            </span><span class="c1">// Check if error is due to unsupported features
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="c1">// vs actual syntax errors
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span><span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="n">error_msg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">to_string</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">error_msg</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="s">&#34;backreference&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">                </span><span class="o">||</span><span class="w"> </span><span class="n">error_msg</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="s">&#34;look&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">                </span><span class="c1">// Potentially unsafe pattern
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"></span><span class="w">                </span><span class="nb">Ok</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">                </span><span class="c1">// Syntax error
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"></span><span class="w">                </span><span class="nb">Err</span><span class="p">(</span><span class="n">pyo3</span>::<span class="n">exceptions</span>::<span class="n">PyValueError</span>::<span class="n">new_err</span><span class="p">(</span><span class="n">error_msg</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w"></span><span class="sd">/// Benchmark a pattern to detect slow execution
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="sd"></span><span class="cp">#[pyfunction]</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w"></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">benchmark_pattern</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">    </span><span class="n">pattern</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">    </span><span class="n">test_input</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="w">    </span><span class="n">max_ms</span>: <span class="kt">u64</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">PyResult</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">    </span><span class="c1">// TODO: Implement timeout-based safety check
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// 1. Compile the pattern
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// 2. Run match with timeout
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="c1">// 3. Return false if exceeds max_ms
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1"></span><span class="w">    </span><span class="nb">Ok</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.rs/regex/">Rust regex crate 文件</a> - 完整的 API 文件與效能說明</li>
<li><a href="https://swtch.com/~rsc/regexp/">正則表達式引擎比較</a> - Russ Cox 的經典系列文章</li>
<li><a href="https://pyo3.rs/">PyO3 User Guide</a> - PyO3 完整教學</li>
<li><a href="https://docs.rs/once_cell/">once_cell crate</a> - 延遲初始化最佳實踐</li>
<li><a href="https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS">ReDoS 攻擊與防護</a> - OWASP 安全指南</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/" data-link-title="案例：PyO3 文字解析" data-link-desc="用 PyO3 和 Rust 實現高效能的 Markdown 連結解析器">PyO3 文字解析</a></em>
<em>返回：<a href="/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">模組六：用 Rust 擴展 Python</a></em></p>
]]></content:encoded></item><item><title>案例：自動註冊機制</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Metaclass 實現檢查器的自動註冊。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>HookValidator&lt;/code> 類別包含多個 &lt;code>check_*&lt;/code> 方法，需要在 &lt;code>validate_hook()&lt;/code> 中手動呼叫：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&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="s2">&amp;#34;&amp;#34;&amp;#34;驗證單個 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 前置處理 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 執行各項檢查 - 手動呼叫每個 check 方法&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">issues&lt;/span> &lt;span class="o">=&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">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&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="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&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="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查命名規範&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查共用模組導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查 Hook 輸出格式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查對應的測試檔案是否存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作 ...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>明確的執行順序&lt;/strong>：可以精確控制檢查項的執行順序&lt;/li>
&lt;li>&lt;strong>容易理解呼叫流程&lt;/strong>：閱讀 &lt;code>validate_hook()&lt;/code> 就知道會執行哪些檢查&lt;/li>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：不需要學習額外的抽象概念&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>新增檢查項時：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>需要修改兩處程式碼&lt;/strong>：新增 &lt;code>check_*&lt;/code> 方法 + 修改 &lt;code>validate_hook()&lt;/code> 呼叫&lt;/li>
&lt;li>&lt;strong>容易忘記註冊&lt;/strong>：新增方法後忘記在 &lt;code>validate_hook()&lt;/code> 中呼叫&lt;/li>
&lt;li>&lt;strong>無法動態控制&lt;/strong>：無法在執行時期啟用/停用特定檢查項&lt;/li>
&lt;li>&lt;strong>難以擴展&lt;/strong>：子類別新增檢查項也需要覆寫 &lt;code>validate_hook()&lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>定義檢查方法時&lt;strong>自動註冊&lt;/strong>到執行清單&lt;/li>
&lt;li>支援&lt;strong>優先順序控制&lt;/strong>&lt;/li>
&lt;li>支援&lt;strong>動態啟用/停用&lt;/strong>特定檢查項&lt;/li>
&lt;li>子類別的檢查項&lt;strong>自動繼承&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1用裝飾器標記檢查方法">步驟 1：用裝飾器標記檢查方法&lt;/h4>
&lt;p>首先，我們需要一個方式來標記哪些方法是「檢查器」。裝飾器是最自然的選擇：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">wraps&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check&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="n">priority&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">100&lt;/span>&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 class="n">enabled&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Callable&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> Decorator to mark a method as a checker.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> priority: Execution order (lower runs first)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> enabled: Whether this check is enabled by default
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> description: Human-readable description
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> @check(priority=10, description=&amp;#34;Validate filename format&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> def check_naming(self, hook_path):
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Store metadata on the function object&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_is_checker&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_priority&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">priority&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enabled&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">description&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__doc__&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nd">@wraps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">wrapper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Copy metadata to wrapper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_is_checker&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_priority&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">priority&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enabled&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">description&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__doc__&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">wrapper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">decorator&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用方式：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Metaclass 實現檢查器的自動註冊。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的 <code>HookValidator</code> 類別包含多個 <code>check_*</code> 方法，需要在 <code>validate_hook()</code> 中手動呼叫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># ... 前置處理 ...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 執行各項檢查 - 手動呼叫每個 check 方法</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># ... 實作 ...</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查共用模組導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># ... 實作 ...</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">check_output_format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查 Hook 輸出格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># ... 實作 ...</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查對應的測試檔案是否存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># ... 實作 ...</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>明確的執行順序</strong>：可以精確控制檢查項的執行順序</li>
<li><strong>容易理解呼叫流程</strong>：閱讀 <code>validate_hook()</code> 就知道會執行哪些檢查</li>
<li><strong>簡單直覺</strong>：不需要學習額外的抽象概念</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>新增檢查項時：</p>
<ol>
<li><strong>需要修改兩處程式碼</strong>：新增 <code>check_*</code> 方法 + 修改 <code>validate_hook()</code> 呼叫</li>
<li><strong>容易忘記註冊</strong>：新增方法後忘記在 <code>validate_hook()</code> 中呼叫</li>
<li><strong>無法動態控制</strong>：無法在執行時期啟用/停用特定檢查項</li>
<li><strong>難以擴展</strong>：子類別新增檢查項也需要覆寫 <code>validate_hook()</code></li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li>定義檢查方法時<strong>自動註冊</strong>到執行清單</li>
<li>支援<strong>優先順序控制</strong></li>
<li>支援<strong>動態啟用/停用</strong>特定檢查項</li>
<li>子類別的檢查項<strong>自動繼承</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1用裝飾器標記檢查方法">步驟 1：用裝飾器標記檢查方法</h4>
<p>首先，我們需要一個方式來標記哪些方法是「檢查器」。裝飾器是最自然的選擇：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">priority</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Decorator to mark a method as a checker.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        priority: Execution order (lower runs first)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        enabled: Whether this check is enabled by default
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        description: Human-readable description
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        @check(priority=10, description=&#34;Validate filename format&#34;)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        def check_naming(self, hook_path):
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            ...
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Store metadata on the function object</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># Copy metadata to wrapper</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</span></span></span></code></pre></div><p>使用方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check filename format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="o">...</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check library imports&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查共用模組導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><h4 id="步驟-2用-metaclass-收集標記的方法">步驟 2：用 Metaclass 收集標記的方法</h4>
<p>接下來，用 Metaclass 在類別建立時自動收集所有被 <code>@check</code> 標記的方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Metaclass that automatically collects methods marked with @check.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    When a class is created, this metaclass:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    1. Scans all methods for the _is_checker attribute
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    2. Collects them into a _checkers registry
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    3. Sorts by priority for execution order
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">bases</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">namespace</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># Create the class first</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">cls</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">namespace</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Collect checkers from parent classes</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">inherited_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s1">&#39;_checkers&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                <span class="n">inherited_checkers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_checkers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Collect checkers from current class</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">current_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">attr_value</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                <span class="n">current_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_description</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="c1"># Merge: current class can override parent checkers</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">all_checkers</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">inherited_checkers</span><span class="p">,</span> <span class="o">**</span><span class="n">current_checkers</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="c1"># Store as class attribute</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span> <span class="o">=</span> <span class="n">all_checkers</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span></span></span></code></pre></div><h4 id="步驟-3實作優先順序">步驟 3：實作優先順序</h4>
<p>在 Metaclass 中已經收集了優先順序資訊，現在需要一個方法按順序執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Base class for validators with auto-registration.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Provides run_all_checks() to execute registered checkers.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="nf">get_sorted_checkers</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        Get all enabled checkers sorted by priority.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            List of (method_name, checker_info) tuples
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">checkers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># Sort by priority (lower number = higher priority)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">checkers</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">def</span> <span class="nf">run_all_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Run all enabled checkers in priority order.
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            *args, **kwargs: Arguments passed to each checker
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">            Combined list of issues from all checkers
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">all_issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">for</span> <span class="n">method_name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_sorted_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">method_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">method</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="k">if</span> <span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">                    <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="c1"># Optionally handle checker errors</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                    <span class="s1">&#39;level&#39;</span><span class="p">:</span> <span class="s1">&#39;error&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                    <span class="s1">&#39;message&#39;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Checker </span><span class="si">{</span><span class="n">method_name</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="n">all_issues</span></span></span></code></pre></div><h4 id="步驟-4實作啟用停用機制">步驟 4：實作啟用/停用機制</h4>
<p>允許在執行時期動態控制檢查項：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Base class for validators with auto-registration.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1"># Instance-level override for checker states</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">enable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        Enable a specific checker for this instance.
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            checker_name: Name of the checker method
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">if</span> <span class="n">checker_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </span><span class="si">{</span><span class="n">checker_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">disable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        Disable a specific checker for this instance.
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            checker_name: Name of the checker method
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="n">checker_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </span><span class="si">{</span><span class="n">checker_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">is_checker_enabled</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">        Check if a specific checker is enabled.
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">        Instance overrides take precedence over class defaults.
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">if</span> <span class="n">checker_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">checker_name</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;enabled&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">def</span> <span class="nf">get_sorted_checkers</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checkers sorted by priority.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">checkers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">checkers</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checkers</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">        List all available checkers with their status.
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">            List of dicts with checker information
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="k">return</span> <span class="n">result</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Auto-registration pattern using Metaclass.
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">This module demonstrates how to automatically register checker methods
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">using a combination of decorators and metaclasses.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"># ===== Data Classes =====</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Represents a validation issue.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single target.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="n">target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">is_valid</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;True if no errors found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">return</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"># ===== Decorator =====</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">priority</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">description</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    Decorator to mark a method as a checker.
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">        priority: Execution order (lower runs first)
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">        enabled: Whether this check is enabled by default
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">        description: Human-readable description
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="c1"># Store metadata</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="c1"># ===== Metaclass =====</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">    Metaclass that automatically collects @check decorated methods.
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">bases</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">namespace</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">cls</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">namespace</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="c1"># Inherit checkers from parent classes</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">inherited_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s1">&#39;_checkers&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                <span class="n">inherited_checkers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_checkers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="c1"># Collect checkers from current class</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">current_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">attr_value</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                <span class="n">current_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_description</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="c1"># Merge checkers</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">inherited_checkers</span><span class="p">,</span> <span class="o">**</span><span class="n">current_checkers</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="c1"># ===== Base Class =====</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">    Base class providing auto-registration functionality.
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="nf">enable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a checker for this instance.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="k">def</span> <span class="nf">disable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a checker for this instance.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="nf">is_checker_enabled</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if a checker is enabled.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;enabled&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">def</span> <span class="nf">get_sorted_checkers</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get enabled checkers sorted by priority.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">            <span class="p">[(</span><span class="n">n</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">n</span><span class="p">,</span> <span class="n">i</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">n</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">            <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checkers</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all checkers with their status.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">                <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">                <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">                <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">                <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="c1"># ===== Implementation Example =====</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">(</span><span class="n">CheckerBase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="s2">    Hook validator with auto-registered checkers.
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="s2">    Checkers are automatically discovered and executed in priority order.
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="c1"># Pattern constants</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="n">VALID_NAME_PATTERN</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/auto-registration/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span> <span class="k">if</span> <span class="n">project_root</span> <span class="k">else</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="s2">        Validate a hook file by running all enabled checkers.
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="s2">            hook_path: Path to the hook file
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="s2">            ValidationResult with all issues found
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">path</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="c1"># Read file content</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                <span class="n">target</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read file: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="c1"># Prepare context for checkers</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">            <span class="s1">&#39;path&#39;</span><span class="p">:</span> <span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">            <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="s1">&#39;filename&#39;</span><span class="p">:</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="c1"># Run all enabled checkers</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">all_issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="k">for</span> <span class="n">method_name</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_sorted_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">method_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">method</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="k">if</span> <span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                    <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Checker &#39;</span><span class="si">{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">&#39; crashed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check filename format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate hook filename follows naming convention.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;filename&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERN</span><span class="p">,</span> <span class="n">filename</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Filename &#39;</span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#39; doesn&#39;t follow naming convention&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake_case or kebab-case, e.g., check_format.py&#34;</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">
</span></span><span class="line"><span class="ln">232</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check hook_io import&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if hook_io module is properly imported.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="n">has_import</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">has_import</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;hook_io module not imported&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_io import read_hook_input, write_hook_output&#34;</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">
</span></span><span class="line"><span class="ln">252</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check output format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="k">def</span> <span class="nf">check_output_format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if proper output functions are used.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">            <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using print(json.dumps(...)) instead of write_hook_output()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Replace with: write_hook_output(output_dict)&#34;</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">40</span><span class="p">,</span> <span class="n">enabled</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check test file exists&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if corresponding test file exists.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;path&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl">        <span class="n">test_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">test_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;No test file found: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Create test at: </span><span class="si">{</span><span class="n">test_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="c1"># ===== Extended Example: Subclass =====</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">
</span></span><span class="line"><span class="ln">290</span><span class="cl"><span class="k">class</span> <span class="nc">StrictHookValidator</span><span class="p">(</span><span class="n">HookValidator</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="s2">    Extended validator with additional checks.
</span></span></span><span class="line"><span class="ln">293</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">294</span><span class="cl"><span class="s2">    Inherits all checks from HookValidator and adds more.
</span></span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check shebang line&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="k">def</span> <span class="nf">check_shebang</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if file starts with proper shebang.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">content</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;#!/usr/bin/env python&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing shebang line&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: #!/usr/bin/env python3&#34;</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">25</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check docstring exists&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="k">def</span> <span class="nf">check_docstring</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if module has a docstring.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="c1"># Simple check: look for triple quotes near the start</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)[:</span><span class="mi">10</span><span class="p">]</span>  <span class="c1"># First 10 lines</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">        <span class="n">has_docstring</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="s1">&#39;&#34;&#34;&#34;&#39;</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">or</span> <span class="s2">&#34;&#39;&#39;&#39;&#34;</span> <span class="ow">in</span> <span class="n">line</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">
</span></span><span class="line"><span class="ln">322</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">has_docstring</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Module docstring not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add a docstring at the top of the file&#34;</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">
</span></span><span class="line"><span class="ln">331</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Auto-Registration Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">
</span></span><span class="line"><span class="ln">338</span><span class="cl">    <span class="c1"># Create validator</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="c1"># List all registered checkers</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">[Registered Checkers]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">344</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> (priority: </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;        </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="c1"># Enable disabled checker</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">[Enable check_test_exists]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="n">validator</span><span class="o">.</span><span class="n">enable_checker</span><span class="p">(</span><span class="s1">&#39;check_test_exists&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">        <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;check_test_exists&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">355</span><span class="cl">
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="c1"># Demo with extended validator</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">[Extended Validator - StrictHookValidator]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">    <span class="n">strict_validator</span> <span class="o">=</span> <span class="n">StrictHookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">strict_validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> (priority: </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">
</span></span><span class="line"><span class="ln">363</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Create validator instance</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">(</span><span class="n">project_root</span><span class="o">=</span><span class="s2">&#34;/path/to/project&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># List available checkers</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="s1">&#39;ON&#39;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s1">&#39;OFF&#39;</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># [ON] check_naming_convention</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># [ON] check_lib_imports</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># [ON] check_output_format</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># [OFF] check_test_exists</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Enable/disable checkers dynamically</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">enable_checker</span><span class="p">(</span><span class="s1">&#39;check_test_exists&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">disable_checker</span><span class="p">(</span><span class="s1">&#39;check_output_format&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Validate a hook file</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;.claude/hooks/my-hook.py&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Valid: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"># Create extended validator with more checks</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">strict</span> <span class="o">=</span> <span class="n">StrictHookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Total checkers: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">strict</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">())</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># Inherits parent&#39;s checkers</span></span></span></code></pre></div><h2 id="替代方案__init_subclass__">替代方案：<code>__init_subclass__</code></h2>
<p>Python 3.6 引入了 <code>__init_subclass__</code>，可以在不使用 Metaclass 的情況下實現部分功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">CheckerBase</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Base class using __init_subclass__ for auto-registration.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Simpler than metaclass, but less powerful.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init_subclass__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># Collect checkers from this subclass</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">attr</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">attr</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_description</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">(</span><span class="n">CheckerBase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validator using __init_subclass__.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check naming convention.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">check_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check imports.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="__init_subclass__-vs-metaclass"><code>__init_subclass__</code> vs Metaclass</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th><code>__init_subclass__</code></th>
          <th>Metaclass</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>
      <tr>
          <td>推薦情況</td>
          <td>優先使用</td>
          <td>需要進階功能時</td>
      </tr>
  </tbody>
</table>
<p><strong>原則</strong>：如果 <code>__init_subclass__</code> 能滿足需求，優先使用它。</p>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>手動註冊</th>
          <th>Metaclass 自動註冊</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式碼重複</td>
          <td>高（每次新增都要改兩處）</td>
          <td>低（只需加裝飾器）</td>
      </tr>
      <tr>
          <td>理解難度</td>
          <td>低</td>
          <td>中（需理解 Metaclass）</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>由 priority 決定</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p>適合使用：</p>
<ul>
<li><strong>插件系統</strong>：需要自動發現和載入插件</li>
<li><strong>框架開發</strong>：Django admin、pytest fixtures 等</li>
<li><strong>大量相似元件</strong>：多個檢查器/處理器需統一管理</li>
<li><strong>需要動態控制</strong>：執行時期啟用/停用功能</li>
</ul>
<p>不建議使用：</p>
<ul>
<li><strong>只有少數幾個檢查項</strong>：手動維護更簡單</li>
<li><strong>團隊不熟悉 Metaclass</strong>：增加維護負擔</li>
<li><strong>簡單的應用程式</strong>：過度工程</li>
<li><strong>執行順序非常重要</strong>：手動呼叫更明確</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<p>實作一個簡單的命令註冊系統：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">CommandRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    命令註冊系統
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    需求：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    1. 用 @command(name=&#34;xxx&#34;) 裝飾器註冊命令
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    2. 提供 execute(name, *args) 執行指定命令
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    3. 提供 list_commands() 列出所有命令
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">MyApp</span><span class="p">(</span><span class="n">CommandRegistry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nd">@command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;greet&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">say_hello</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nd">@command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;add&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">add_numbers</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">MyApp</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;greet&#34;</span><span class="p">,</span> <span class="s2">&#34;World&#34;</span><span class="p">))</span>  <span class="c1"># Hello, World!</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">list_commands</span><span class="p">())</span>  <span class="c1"># [&#39;add&#39;, &#39;greet&#39;]</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<p>新增檢查項的相依性管理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">depends_on</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;check_file_exists&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">check_content</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;只有在檔案存在檢查通過後才執行&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><p>提示：</p>
<ol>
<li>在 <code>@check</code> 裝飾器加入 <code>depends_on</code> 參數</li>
<li>在 <code>run_all_checks()</code> 中追蹤每個檢查項的結果</li>
<li>檢查相依項是否通過再執行</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<p>實作跨模組的檢查項發現（類似 pytest）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># checkers/naming.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_naming</span><span class="p">(</span><span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># checkers/imports.py</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">check_imports</span><span class="p">(</span><span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># main.py</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">discover_checkers</span><span class="p">(</span><span class="s1">&#39;checkers/&#39;</span><span class="p">)</span>  <span class="c1"># 自動載入目錄下的所有檢查項</span></span></span></code></pre></div><p>提示：</p>
<ol>
<li>使用 <code>importlib</code> 動態載入模組</li>
<li>掃描模組中帶有 <code>_is_checker</code> 標記的函式</li>
<li>將函式綁定為實例方法</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#metaclasses">Python Metaclass 官方文件</a></li>
<li><a href="https://github.com/django/django/blob/main/django/db/models/base.py">Django Model 的 Metaclass 實作</a></li>
<li><a href="https://peps.python.org/pep-0487/">PEP 487 &ndash; <code>__init_subclass__</code></a></li>
<li><a href="https://docs.pytest.org/en/latest/how-to/writing_plugins.html">pytest 插件發現機制</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field</a></em></p>
]]></content:encoded></item><item><title>案例：並行 Hook 驗證</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法，展示如何使用 &lt;code>ThreadPoolExecutor&lt;/code> 配合 &lt;code>submit()&lt;/code> + &lt;code>as_completed()&lt;/code> 實現並行驗證，並加入即時進度報告功能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查&lt;/a>（使用 &lt;code>map()&lt;/code> 的基本並行模式）&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法需要驗證多個 Hook 檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證問題描述&amp;#34;&amp;#34;&amp;#34;&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">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&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">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;單個 Hook 的驗證結果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="n">is_compliant&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__post_init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_compliant&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="n">issue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">issue&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">issues&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證單個 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證項目：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 命名規範檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 共用模組導入檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸出格式檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 測試存在性檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 檔案不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 讀取檔案並執行各項檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無法讀取 Hook 檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序驗證所有 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 找出所有 .py 檔案並依序驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hook_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，易於理解和除錯&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：按檔案名稱排序，輸出一致&lt;/li>
&lt;li>&lt;strong>錯誤處理明確&lt;/strong>：每個驗證結果立即可用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當 Hook 數量增加時：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法，展示如何使用 <code>ThreadPoolExecutor</code> 配合 <code>submit()</code> + <code>as_completed()</code> 實現並行驗證，並加入即時進度報告功能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查</a>（使用 <code>map()</code> 的基本並行模式）</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法需要驗證多個 Hook 檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">        驗證單個 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        驗證項目：
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        - 命名規範檢查
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        - 共用模組導入檢查
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        - 輸出格式檢查
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        - 測試存在性檢查
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">hook_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="c1"># 讀取檔案並執行各項檢查</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">hook_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">        同步版本：依序驗證所有 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">                            <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 目錄不存在: </span><span class="si">{</span><span class="n">hooks_dir</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="c1"># 找出所有 .py 檔案並依序驗證</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單直覺</strong>：循序執行，易於理解和除錯</li>
<li><strong>結果有序</strong>：按檔案名稱排序，輸出一致</li>
<li><strong>錯誤處理明確</strong>：每個驗證結果立即可用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當 Hook 數量增加時：</p>
<ul>
<li><strong>執行時間線性增長</strong>：20 個 Hook，每個 0.1 秒 = 2 秒</li>
<li><strong>無法利用 I/O 等待時間</strong>：讀取檔案時 CPU 閒置</li>
<li><strong>使用者體驗差</strong>：大量 Hook 時沒有進度回饋</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量同步版本的執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 20 個 Hook，每個 0.1 秒</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 總計：20 * 0.1 = 2.0 秒</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="map-vs-submit--as_completed">map() vs submit() + as_completed()</h3>
<p>在「並行檔案檢查」案例中，我們使用 <code>executor.map()</code> 實現並行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 結果按輸入順序返回
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 必須等所有任務完成才能取得結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 無法即時報告進度
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p><strong><code>map()</code> 的限制</strong>：</p>
<ol>
<li><strong>無法即時取得結果</strong>：必須等待所有任務完成</li>
<li><strong>無法追蹤進度</strong>：不知道哪些任務已完成</li>
<li><strong>異常處理受限</strong>：遇到第一個異常就停止迭代</li>
</ol>
<p><strong><code>submit()</code> + <code>as_completed()</code> 的優勢</strong>：</p>
<ol>
<li><strong>即時取得完成的結果</strong>：任務完成就能處理</li>
<li><strong>支援進度報告</strong>：可以計算已完成數量</li>
<li><strong>更靈活的異常處理</strong>：可以逐一處理每個任務的異常</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 結果按完成順序返回
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 可以即時報告進度
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 更完善的錯誤處理
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="c1"># 個別任務失敗不影響其他任務</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="實作進度報告">實作進度報告</h3>
<p><code>as_completed()</code> 的核心優勢是支援即時進度報告：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    帶進度報告的並行驗證
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        progress_callback: 進度回調函式
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            - 參數: (已完成數, 總數, 當前檔案名)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        list[ValidationResult]: 驗證結果列表
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># 提交所有任務，記錄 Future 到路徑的映射</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">for</span> <span class="n">completed_count</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="c1"># 呼叫進度回調</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed_count</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡單的進度顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="c1"># \r 回到行首覆蓋顯示</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>  <span class="c1"># 完成後換行</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">def</span> <span class="nf">demo_progress</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="n">hooks_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;.claude/hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;開始驗證 Hook 檔案...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="c1"># 統計結果</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>進度報告的變體</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">completed</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">total</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">current_file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">elapsed_seconds</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">estimated_remaining</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressTracker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度追蹤器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">=</span> <span class="n">total</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;更新進度並返回資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span><span class="p">)</span><span class="o">.</span><span class="n">total_seconds</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 估算剩餘時間</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">avg_time</span> <span class="o">=</span> <span class="n">elapsed</span> <span class="o">/</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="n">avg_time</span> <span class="o">*</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">return</span> <span class="n">ProgressInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">completed</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">total</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">current_file</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">elapsed_seconds</span><span class="o">=</span><span class="n">elapsed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">estimated_remaining</span><span class="o">=</span><span class="n">remaining</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_rich_progress</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    帶詳細進度資訊的驗證
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">    顯示：完成數、百分比、已用時間、預估剩餘時間
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">tracker</span> <span class="o">=</span> <span class="n">ProgressTracker</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">future_to_path</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">            <span class="c1"># 更新並顯示進度</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">            <span class="n">info</span> <span class="o">=</span> <span class="n">tracker</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">            <span class="n">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">
</span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="k">def</span> <span class="nf">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">:</span> <span class="n">ProgressInfo</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">    <span class="s2">&#34;&#34;&#34;顯示詳細進度&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">20</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">
</span></span><span class="line"><span class="ln">86</span><span class="cl">    <span class="n">elapsed_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">elapsed_seconds</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="n">remaining_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">estimated_remaining</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">
</span></span><span class="line"><span class="ln">89</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="si">}</span><span class="s2"> &#34;</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;(</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) | &#34;</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;已用: </span><span class="si">{</span><span class="n">elapsed_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">93</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;剩餘: </span><span class="si">{</span><span class="n">remaining_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">current_file</span><span class="p">[:</span><span class="mi">20</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">95</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">96</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">97</span><span class="cl">
</span></span><span class="line"><span class="ln">98</span><span class="cl">    <span class="k">if</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">==</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">99</span><span class="cl">        <span class="nb">print</span><span class="p">()</span></span></span></code></pre></div><h3 id="錯誤處理策略">錯誤處理策略</h3>
<p><code>submit()</code> + <code>as_completed()</code> 提供更細緻的錯誤處理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">CANCELLED</span> <span class="o">=</span> <span class="s2">&#34;cancelled&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">    處理的錯誤類型：
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    - 驗證邏輯錯誤
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">    - 單一任務超時
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    - 任務被取消
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        timeout_per_file: 單一檔案的超時秒數
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">        list[DetailedResult]: 包含狀態的詳細結果
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="c1"># 設定單一結果的超時</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="k">def</span> <span class="nf">summarize_results</span><span class="p">(</span><span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="s2">&#34;&#34;&#34;彙總驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">summary</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="s2">&#34;success&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="s2">&#34;failed&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="s2">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="s2">&#34;non_compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;success&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span> <span class="ow">and</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;non_compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">elif</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;timeout&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;failed&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="k">return</span> <span class="n">summary</span></span></span></code></pre></div><p><strong>錯誤處理模式比較</strong>：</p>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th><code>map()</code></th>
          <th><code>as_completed()</code></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>
  </tbody>
</table>
<h2 id="完整程式碼">完整程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Hook 驗證工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 ThreadPoolExecutor + as_completed 實現：
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">- 並行驗證多個 Hook 檔案
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">- 即時進度報告
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">- 完善的錯誤處理
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1"># ===== 資料結構 =====</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="c1"># ===== 驗證器 =====</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器（簡化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_naming</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_naming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERNS</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查導入規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;未導入 hook_io 模組&#34;</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"># ===== 並行驗證 =====</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_sync</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="s2">    同步版本（基準對照）
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">
</span></span><span class="line"><span class="ln">155</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">
</span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="s2">        max_workers: 最大執行緒數
</span></span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="s2">        progress_callback: 進度回調 (completed, total, filename)
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="s2">        驗證結果列表
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="c1"># 依完成順序處理</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">for</span> <span class="n">completed</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">
</span></span><span class="line"><span class="ln">220</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl"><span class="c1"># ===== 進度顯示 =====</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">
</span></span><span class="line"><span class="ln">269</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度條顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">:</span><span class="s2">&lt;30</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="c1"># ===== 效能測試 =====</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">
</span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="s2">    比較不同策略的執行時間
</span></span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">
</span></span><span class="line"><span class="ln">292</span><span class="cl">    <span class="c1"># 同步版本</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">        <span class="n">validate_all_hooks_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="c1"># map() 版本</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">        <span class="n">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;map&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="c1"># as_completed() 版本</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">        <span class="n">validate_all_hooks_async</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">
</span></span><span class="line"><span class="ln">320</span><span class="cl"><span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範並行 Hook 驗證&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 並行 Hook 驗證示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">
</span></span><span class="line"><span class="ln">324</span><span class="cl">    <span class="c1"># 建立測試用的 Hook 檔案</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/tmp/test_hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">test_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;hook-</span><span class="si">{</span><span class="n">i</span><span class="si">:</span><span class="s2">02d</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">hook_file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;&#39;&#39;#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">332</span><span class="cl"><span class="s1">&#34;&#34;&#34;Test hook </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">334</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">335</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln">336</span><span class="cl"><span class="s1">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">337</span><span class="cl"><span class="s1">    write_hook_output(</span><span class="se">{{</span><span class="s1">&#34;status&#34;: &#34;ok&#34;</span><span class="se">}}</span><span class="s1">)
</span></span></span><span class="line"><span class="ln">338</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">339</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">340</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln">341</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">        <span class="n">hook_files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試檔案數: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">    <span class="k">for</span> <span class="n">strategy</span><span class="p">,</span> <span class="n">elapsed</span> <span class="ow">in</span> <span class="n">times</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">strategy</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">
</span></span><span class="line"><span class="ln">352</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 帶進度的驗證</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 帶進度報告的驗證:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">   合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="c1"># 清理測試檔案</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">rmdir</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">
</span></span><span class="line"><span class="ln">370</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">    <span class="n">demo</span><span class="p">()</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<h3 id="測試環境">測試環境</h3>
<ul>
<li>Python 3.11</li>
<li>20 個 Hook 檔案</li>
<li>每個驗證包含：檔案讀取、正則匹配、路徑檢查</li>
</ul>
<h3 id="測試結果">測試結果</h3>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>執行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同步 (基準)</td>
          <td>0.85s</td>
          <td>1.0x</td>
      </tr>
      <tr>
          <td>map()</td>
          <td>0.25s</td>
          <td>3.4x</td>
      </tr>
      <tr>
          <td>as_completed()</td>
          <td>0.26s</td>
          <td>3.3x</td>
      </tr>
      <tr>
          <td>as_completed() + 進度</td>
          <td>0.27s</td>
          <td>3.1x</td>
      </tr>
  </tbody>
</table>
<p><strong>觀察</strong>：</p>
<ol>
<li><code>map()</code> 和 <code>as_completed()</code> 效能相近</li>
<li>進度報告的額外開銷約 3-5%</li>
<li>實際加速比接近 <code>min(hook_count, max_workers)</code></li>
</ol>
<h3 id="不同檔案數量的效能">不同檔案數量的效能</h3>





<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">Hook 數量    同步      並行(4)    加速比
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------------------------------
</span></span><span class="line"><span class="ln">3</span><span class="cl">5           0.21s     0.08s      2.6x
</span></span><span class="line"><span class="ln">4</span><span class="cl">10          0.42s     0.14s      3.0x
</span></span><span class="line"><span class="ln">5</span><span class="cl">20          0.85s     0.26s      3.3x
</span></span><span class="line"><span class="ln">6</span><span class="cl">50          2.10s     0.58s      3.6x
</span></span><span class="line"><span class="ln">7</span><span class="cl">100         4.25s     1.12s      3.8x</span></span></code></pre></div><p>加速比隨檔案數量增加而提升，趨近於 <code>max_workers</code> 數量。</p>
<h2 id="設計權衡">設計權衡</h2>
<h3 id="map-vs-as_completed-選擇指南">map() vs as_completed() 選擇指南</h3>





<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">需要並行處理多個獨立任務？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 是 → 需要即時進度報告？
</span></span><span class="line"><span class="ln">3</span><span class="cl">│        ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">4</span><span class="cl">│        └── 否 → 需要細緻的錯誤處理？
</span></span><span class="line"><span class="ln">5</span><span class="cl">│                 ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">6</span><span class="cl">│                 └── 否 → 使用 map()（更簡潔）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 否 → 直接循序執行</span></span></code></pre></div><h3 id="比較表">比較表</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>map()</th>
          <th>submit() + as_completed()</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>
      <tr>
          <td>適用場景</td>
          <td>批次處理，不需即時回饋</td>
          <td>需要進度報告或細緻錯誤處理</td>
      </tr>
  </tbody>
</table>
<h3 id="進度報告的開銷">進度報告的開銷</h3>
<table>
  <thead>
      <tr>
          <th>進度報告方式</th>
          <th>額外開銷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>無</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>簡單計數器</td>
          <td>~1%</td>
      </tr>
      <tr>
          <td>進度條（無 flush）</td>
          <td>~2%</td>
      </tr>
      <tr>
          <td>進度條（每次 flush）</td>
          <td>~5%</td>
      </tr>
      <tr>
          <td>詳細進度（含時間估算）</td>
          <td>~8%</td>
      </tr>
  </tbody>
</table>
<p>對於大量任務（&gt;100），建議每 N 個任務更新一次進度，而非每個任務都更新。</p>
<h2 id="練習">練習</h2>
<h3 id="練習-1加入跳過已驗證功能">練習 1：加入「跳過已驗證」功能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cache</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationResult</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    只驗證快取中沒有的檔案
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 檢查 cache 中是否已有結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 只對新檔案提交任務
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 合併快取結果和新結果
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-2實作取消機制">練習 2：實作取消機制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cancel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">should_cancel</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">bool</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    支援取消的並行驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    當 should_cancel() 返回 True 時，取消所有未完成的任務。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 使用 future.cancel() 取消未開始的任務
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 已開始的任務無法取消，需等待完成
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 返回已完成的結果
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-3實作優先順序">練習 3：實作優先順序</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_priority</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">priority_fn</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Path</span><span class="p">],</span> <span class="nb">int</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    按優先順序驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    高優先順序的檔案先被驗證。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 按優先順序排序後提交
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 但 as_completed 仍按完成順序返回
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 考慮使用 PriorityQueue 控制提交順序
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題實作可暫停恢復的驗證">挑戰題：實作可暫停/恢復的驗證</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">PausableValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    可暫停和恢復的驗證器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        validator = PausableValidator(hook_files)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        validator.start()
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        validator.pause()  # 暫停，已提交的任務會完成
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        validator.resume()  # 恢復
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        results = validator.get_results()
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    - 使用 threading.Event 控制暫停
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    - 追蹤已完成和未開始的任務
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    - 恢復時只提交剩餘任務
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hook_files</span> <span class="o">=</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_paused</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">pause</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">resume</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">get_results</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed">as_completed 與 wait 的差異</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#future-objects">Future 物件的方法</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></em></p>
]]></content:encoded></item><item><title>案例：並行 I/O 操作</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/parallel-io/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/parallel-io/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/git_utils.py&lt;/code> 的實際程式碼，展示如何用 &lt;code>asyncio.gather&lt;/code> 和 &lt;code>TaskGroup&lt;/code> 實現高效的並行 I/O 操作。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>git_utils.py&lt;/code> 的 &lt;code>get_worktree_list()&lt;/code> 取得 worktree 列表後，如果要檢查每個 worktree 的狀態，需要逐一呼叫：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees_sync&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序檢查每個 worktree 的狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 狀態} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&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">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&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="n">results&lt;/span> &lt;span class="o">=&lt;/span> &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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Every call blocks until completion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_all_branches_sync&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序取得每個 worktree 的分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 分支名} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">wt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">wt&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Each command waits for the previous one&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-current&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;unknown&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，容易理解和除錯&lt;/li>
&lt;li>&lt;strong>錯誤處理明確&lt;/strong>：每個操作的結果立即可用&lt;/li>
&lt;li>&lt;strong>資源友善&lt;/strong>：一次只執行一個進程&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當 worktree 數量增加時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>執行時間線性增長&lt;/strong>：10 個 worktree，每個 0.2 秒 = 2 秒&lt;/li>
&lt;li>&lt;strong>無法利用 I/O 等待時間&lt;/strong>：等待一個命令完成時，CPU 閒置&lt;/li>
&lt;li>&lt;strong>使用者體驗差&lt;/strong>：大量 worktree 時響應緩慢&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark_sync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&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="s2">&amp;#34;&amp;#34;&amp;#34;測量同步版本的執行時間&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&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="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&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"># Simulate I/O wait (actual git command)&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">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10 worktrees, each taking 0.2s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># Total: 10 * 0.2 = 2.0 seconds&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>並行執行&lt;/strong>多個獨立的 I/O 操作&lt;/li>
&lt;li>&lt;strong>正確處理&lt;/strong>部分失敗的情況&lt;/li>
&lt;li>&lt;strong>支援取消&lt;/strong>和超時機制&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1使用-asynciogather">步驟 1：使用 asyncio.gather&lt;/h4>
&lt;p>&lt;code>asyncio.gather&lt;/code> 是並行執行多個協程最直接的方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_run_git_command&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="n">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&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 class="n">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">10.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 非同步執行 git 命令（詳見上一章）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_subprocess_exec&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">stderr&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">PIPE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">stdout&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait_for&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">communicate&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TimeoutError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kill&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Command timed out after &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">timeout&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;git command not found&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_all_worktrees_basic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 asyncio.gather 並行檢查所有 worktree
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> worktrees: worktree 路徑列表
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict[str, str]: {路徑: 狀態} 映射
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check a single worktree and return (path, status)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_run_git_command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Create tasks for all worktrees&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">check_one&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Execute all tasks in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Convert list of tuples to dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">&lt;span class="c1"># Usage example&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">demo_basic&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="n">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;/path/to/repo1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/path/to/repo2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;/path/to/repo3&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="c1"># All three checks run in parallel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="c1"># If each takes 0.2s, total time is ~0.2s, not 0.6s&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="n">statuses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">check_all_worktrees_basic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">worktrees&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">statuses&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;clean&amp;#39;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">status&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&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;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/git_utils.py</code> 的實際程式碼，展示如何用 <code>asyncio.gather</code> 和 <code>TaskGroup</code> 實現高效的並行 I/O 操作。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess</a></li>
<li><a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>git_utils.py</code> 的 <code>get_worktree_list()</code> 取得 worktree 列表後，如果要檢查每個 worktree 的狀態，需要逐一呼叫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">run_git_command</span><span class="p">,</span> <span class="n">get_worktree_list</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_all_worktrees_sync</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    同步版本：依序檢查每個 worktree 的狀態
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Every call blocks until completion</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">def</span> <span class="nf">get_all_branches_sync</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    同步版本：依序取得每個 worktree 的分支名稱
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 分支名} 映射
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># Each command waits for the previous one</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單直覺</strong>：循序執行，容易理解和除錯</li>
<li><strong>錯誤處理明確</strong>：每個操作的結果立即可用</li>
<li><strong>資源友善</strong>：一次只執行一個進程</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當 worktree 數量增加時：</p>
<ul>
<li><strong>執行時間線性增長</strong>：10 個 worktree，每個 0.2 秒 = 2 秒</li>
<li><strong>無法利用 I/O 等待時間</strong>：等待一個命令完成時，CPU 閒置</li>
<li><strong>使用者體驗差</strong>：大量 worktree 時響應緩慢</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_sync</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量同步版本的執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><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="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># Simulate I/O wait (actual git command)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 10 worktrees, each taking 0.2s</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Total: 10 * 0.2 = 2.0 seconds</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>並行執行</strong>多個獨立的 I/O 操作</li>
<li><strong>正確處理</strong>部分失敗的情況</li>
<li><strong>支援取消</strong>和超時機制</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1使用-asynciogather">步驟 1：使用 asyncio.gather</h4>
<p><code>asyncio.gather</code> 是並行執行多個協程最直接的方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    非同步執行 git 命令（詳見上一章）
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_basic</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    使用 asyncio.gather 並行檢查所有 worktree
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check a single worktree and return (path, status)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="c1"># Create tasks for all worktrees</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="c1"># Execute all tasks in parallel</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="c1"># Convert list of tuples to dict</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"># Usage example</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_basic</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;/path/to/repo1&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/to/repo2&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/to/repo3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl">    <span class="c1"># All three checks run in parallel</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">    <span class="c1"># If each takes 0.2s, total time is ~0.2s, not 0.6s</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_basic</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="s1">&#39;clean&#39;</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">status</span> <span class="k">else</span> <span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>重點說明</strong>：</p>
<ul>
<li><code>asyncio.gather(*tasks)</code> 同時啟動所有協程</li>
<li>等待所有協程完成後，返回結果列表</li>
<li>結果順序與輸入任務順序一致</li>
</ul>
<h4 id="步驟-2處理錯誤return_exceptions">步驟 2：處理錯誤（return_exceptions）</h4>
<p>預設情況下，<code>gather</code> 在遇到第一個異常時會傳播該異常。使用 <code>return_exceptions=True</code> 可以收集所有結果，包括異常：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_safe</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="ne">Exception</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    安全版本：使用 return_exceptions 處理部分失敗
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    即使某些 worktree 檢查失敗，仍然返回其他的結果。
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        dict: {路徑: 狀態或例外} 映射
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check with potential exception&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span>  <span class="c1"># Shorter timeout</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="c1"># Raise exception for failed commands</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Git command failed: </span><span class="si">{</span><span class="n">output</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="c1"># return_exceptions=True: exceptions become results, not propagated</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># Process results, handling both successes and exceptions</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;error: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="c1"># result is (path, status) tuple</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">_</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status</span> <span class="k">else</span> <span class="s2">&#34;clean&#34;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">return</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_error_handling</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範錯誤處理&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="s2">&#34;/valid/repo1&#34;</span><span class="p">,</span>      <span class="c1"># Works</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="s2">&#34;/invalid/path&#34;</span><span class="p">,</span>     <span class="c1"># Fails</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;/valid/repo2&#34;</span><span class="p">,</span>      <span class="c1"># Works</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_safe</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;error:&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[FAILED] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[OK] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="c1"># [OK] /valid/repo1: clean</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="c1"># [FAILED] /invalid/path: error: Git command failed: ...</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="c1"># [OK] /valid/repo2: M file.txt</span></span></span></code></pre></div><p><strong><code>return_exceptions</code> 行為對比</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">compare_exception_handling</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">might_fail</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2"> failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">return</span> <span class="n">n</span> <span class="o">*</span> <span class="mi">10</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="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">might_fail</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># Without return_exceptions (default)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>  <span class="c1"># Raises ValueError</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># Only see first error, others lost</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># With return_exceptions=True</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># results: [0, 10, ValueError(&#39;Task 2 failed&#39;), 30, 40]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: Failed - </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: Success - </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-3使用-taskgrouppython-311">步驟 3：使用 TaskGroup（Python 3.11+）</h4>
<p>Python 3.11 引入的 <code>TaskGroup</code> 提供更好的結構化並行控制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_worktrees_taskgroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    使用 TaskGroup 並行檢查所有 worktree
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    TaskGroup 特性：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 任一任務失敗時，自動取消其他任務
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 明確的作用域（context manager）
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 異常聚合（ExceptionGroup）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check worktree and store result in shared dict&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1"># All tasks complete when exiting the context</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo_taskgroup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範 TaskGroup 的基本用法&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;/repo1&#34;</span><span class="p">,</span> <span class="s2">&#34;/repo2&#34;</span><span class="p">,</span> <span class="s2">&#34;/repo3&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="c1"># Python 3.11+ except* syntax for ExceptionGroup</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">for</span> <span class="n">exc</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task failed: </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>TaskGroup 的錯誤處理模式</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">taskgroup_error_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範 TaskGroup 的異常處理&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">task_might_fail</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">should_fail</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="n">should_fail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> succeeded&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;A&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="kc">True</span><span class="p">))</span>   <span class="c1"># Will fail</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">task_might_fail</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">))</span>  <span class="c1"># Gets cancelled</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Caught </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> errors:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">for</span> <span class="n">exc</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">exc</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># When B fails:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># 1. TaskGroup cancels C (even though it would succeed)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 2. Waits for all tasks to finish</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># 3. Raises ExceptionGroup with the ValueError</span></span></span></code></pre></div><h4 id="步驟-4並行與循序的混合模式">步驟 4：並行與循序的混合模式</h4>
<p>實際應用中，常需要混合並行和循序操作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_worktrees_batched</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="n">batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    分批並行處理：控制同時執行的任務數量
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    適用場景：
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    - 避免同時開啟過多進程
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    - 防止 API rate limiting
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    - 控制資源使用
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">        batch_size: 每批並行數量
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="c1"># Process in batches</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">worktrees</span><span class="p">),</span> <span class="n">batch_size</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="n">batch</span> <span class="o">=</span> <span class="n">worktrees</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span> <span class="o">+</span> <span class="n">batch_size</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="c1"># Process batch in parallel</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="n">batch_results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="o">*</span><span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">batch</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="c1"># Collect results</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span><span class="n">batch_results</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_worktrees_semaphore</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">    使用 Semaphore 限制並行數量
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">    比分批更靈活：任務完成後立即啟動新任務，
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">    而不是等整批完成。
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        max_concurrent: 最大同時執行數
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">        dict[str, str]: {路徑: 狀態} 映射
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check worktree with concurrency limit&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="c1"># At most max_concurrent tasks run this block</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="c1"># Launch all tasks (they&#39;ll wait at semaphore if needed)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">pipeline_example</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    示範流水線模式：前一步的輸出是後一步的輸入
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="c1"># Step 1: Get worktree list (single operation)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="c1"># Step 2: Check status in parallel</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">check_all_worktrees_basic</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="c1"># Step 3: For dirty repos, get detailed diff (parallel)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">dirty_paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span><span class="p">,</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="n">s</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_diff</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">diff</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;diff&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">diff</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="n">diffs</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="o">*</span><span class="p">[</span><span class="n">get_diff</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">dirty_paths</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;statuses&#34;</span><span class="p">:</span> <span class="n">statuses</span><span class="p">,</span> <span class="s2">&#34;diffs&#34;</span><span class="p">:</span> <span class="n">diffs</span><span class="p">}</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 I/O 操作工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 asyncio.gather 和 TaskGroup 實現高效的並行 Git 操作。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># ===== Core async function =====</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">10.0</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    非同步執行 git 命令
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        args: git 命令參數列表
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">        cwd: 執行目錄
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        timeout: 超時時間（秒）
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        (是否成功, 輸出或錯誤訊息)
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="n">process</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_subprocess_exec</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="n">stderr</span><span class="o">=</span><span class="n">asyncio</span><span class="o">.</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">                <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">            <span class="n">process</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="k">await</span> <span class="n">process</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Command timed out after </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="k">if</span> <span class="n">process</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">stderr</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;git command not found&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取 worktree 列表&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;worktree&#34;</span><span class="p">,</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">current_worktree</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">output</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;worktree &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">            <span class="n">current_worktree</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;path&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">[</span><span class="mi">9</span><span class="p">:]}</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;branch &#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">7</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="k">if</span> <span class="n">branch_ref</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;refs/heads/&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                <span class="n">branch_ref</span> <span class="o">=</span> <span class="n">branch_ref</span><span class="p">[</span><span class="mi">11</span><span class="p">:]</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">branch_ref</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="k">elif</span> <span class="n">line</span> <span class="o">==</span> <span class="s2">&#34;detached&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="n">current_worktree</span><span class="p">[</span><span class="s2">&#34;detached&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="k">if</span> <span class="n">current_worktree</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="n">worktrees</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_worktree</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="k">return</span> <span class="n">worktrees</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="c1"># ===== Parallel strategies =====</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="n">return_exceptions</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">    Strategy 1: asyncio.gather
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">        worktrees: worktree 路徑列表
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        return_exceptions: 是否將異常作為結果返回
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        dict[str, str]: 檢查結果
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="n">return_exceptions</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">results</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;exception: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="n">_</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">output</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status</span> <span class="k">else</span> <span class="s2">&#34;clean&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="k">return</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">    Strategy 2: TaskGroup (Python 3.11+)
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">    One task fails -&gt; all cancelled
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">check_and_store</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_with_semaphore</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">149</span><span class="cl"><span class="s2">    Strategy 3: Semaphore for rate limiting
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="n">semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">semaphore</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">            <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">                <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">                <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_limit</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="c1"># ===== Practical helpers =====</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_worktree_status_report</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">170</span><span class="cl"><span class="s2">    生成完整的 worktree 狀態報告
</span></span></span><span class="line"><span class="ln">171</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">        dict: 包含狀態、分支、變更的完整報告
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="c1"># Step 1: Get worktree list</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">paths</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;No worktrees found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="c1"># Step 2: Parallel operations</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_status</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_last_commit</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;log&#34;</span><span class="p">,</span> <span class="s2">&#34;-1&#34;</span><span class="p">,</span> <span class="s2">&#34;--format=</span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="c1"># Execute all queries in parallel</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="n">status_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_status</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="n">branch_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_branch</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">    <span class="n">commit_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">get_last_commit</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="n">statuses</span><span class="p">,</span> <span class="n">branches</span><span class="p">,</span> <span class="n">commits</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="n">status_task</span><span class="p">,</span> <span class="n">branch_task</span><span class="p">,</span> <span class="n">commit_task</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">
</span></span><span class="line"><span class="ln">213</span><span class="cl">    <span class="c1"># Combine results</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">    <span class="n">report</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="n">status_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">statuses</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">        <span class="n">branch_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="n">commit_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">commits</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="n">report</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="n">status_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">            <span class="s2">&#34;is_clean&#34;</span><span class="p">:</span> <span class="ow">not</span> <span class="n">status_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">            <span class="s2">&#34;last_commit&#34;</span><span class="p">:</span> <span class="n">commit_dict</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">
</span></span><span class="line"><span class="ln">228</span><span class="cl">    <span class="k">return</span> <span class="n">report</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="c1"># ===== Benchmark =====</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">
</span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">benchmark_strategies</span><span class="p">(</span><span class="n">worktrees</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="s2">    比較不同策略的執行時間
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="c1"># Strategy 1: Sequential (baseline)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">    <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;sequential&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">
</span></span><span class="line"><span class="ln">244</span><span class="cl">    <span class="c1"># Strategy 2: gather</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_gather</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;gather&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">
</span></span><span class="line"><span class="ln">249</span><span class="cl">    <span class="c1"># Strategy 3: TaskGroup</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_taskgroup</span><span class="p">(</span><span class="n">worktrees</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;taskgroup&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="c1"># Strategy 4: Semaphore</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="k">await</span> <span class="n">parallel_with_semaphore</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;semaphore(3)&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">
</span></span><span class="line"><span class="ln">261</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範並行 I/O 操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 並行 I/O 操作示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="c1"># Get worktrees</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 獲取 worktree 列表:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="n">branch</span> <span class="o">=</span> <span class="n">wt</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;detached&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">wt</span><span class="p">[</span><span class="s1">&#39;path&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">
</span></span><span class="line"><span class="ln">277</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">        <span class="c1"># Benchmark</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="n">times</span> <span class="o">=</span> <span class="k">await</span> <span class="n">benchmark_strategies</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span><span class="p">,</span> <span class="n">elapsed</span> <span class="ow">in</span> <span class="n">times</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">strategy</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;sequential&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;gather&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="c1"># Full report</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;3. 完整狀態報告:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="n">report</span> <span class="o">=</span> <span class="k">await</span> <span class="n">get_worktree_status_report</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">report</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;clean&#34;</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&#34;is_clean&#34;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;dirty&#34;</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   [</span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;       狀態: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;       最新提交: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;last_commit&#39;</span><span class="p">][:</span><span class="mi">50</span><span class="p">]</span><span class="si">}</span><span class="s2">...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># Get all worktree paths</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">paths</span> <span class="o">=</span> <span class="p">[</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Check all in parallel</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">statuses</span> <span class="o">=</span> <span class="k">await</span> <span class="n">parallel_with_gather</span><span class="p">(</span><span class="n">paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span> <span class="ow">in</span> <span class="n">statuses</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span> <span class="ow">or</span> <span class="s1">&#39;clean&#39;</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h4 id="處理大量-worktree">處理大量 worktree</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_many_worktrees</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理大量 worktree 時使用 semaphore 限制並行數&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># Limit to 10 concurrent git processes</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">parallel_with_semaphore</span><span class="p">(</span><span class="n">paths</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">clean_count</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">values</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">s</span> <span class="ow">or</span> <span class="n">s</span> <span class="o">==</span> <span class="s2">&#34;clean&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">dirty_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span> <span class="o">-</span> <span class="n">clean_count</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Clean: </span><span class="si">{</span><span class="n">clean_count</span><span class="si">}</span><span class="s2">, Dirty: </span><span class="si">{</span><span class="n">dirty_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h4 id="與錯誤重試結合">與錯誤重試結合</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_with_retry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;帶重試的檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</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></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="n">output</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="n">attempt</span> <span class="o">&lt;</span> <span class="n">max_retries</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span> <span class="o">*</span> <span class="p">(</span><span class="n">attempt</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span>  <span class="c1"># Backoff</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="s2">&#34;error: max retries exceeded&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">check_all_with_retry</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;所有檢查都帶重試機制&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_with_retry</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>asyncio.gather</th>
          <th>TaskGroup</th>
          <th>Semaphore</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Python 版本</td>
          <td>3.4+</td>
          <td>3.11+</td>
          <td>3.4+</td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td><code>return_exceptions</code></td>
          <td>自動取消其他任務</td>
          <td>同 gather</td>
      </tr>
      <tr>
          <td>取消行為</td>
          <td>需手動處理</td>
          <td>結構化取消</td>
          <td>需手動處理</td>
      </tr>
      <tr>
          <td>程式碼可讀性</td>
          <td>中等</td>
          <td>較高</td>
          <td>中等</td>
      </tr>
      <tr>
          <td>並行控制</td>
          <td>無內建限制</td>
          <td>無內建限制</td>
          <td>可限制數量</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>一般並行</td>
          <td>全有或全無</td>
          <td>需要限流</td>
      </tr>
  </tbody>
</table>
<h3 id="選擇指南">選擇指南</h3>





<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">需要並行執行多個獨立 I/O 操作？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 是 → 是否需要「一個失敗全部取消」？
</span></span><span class="line"><span class="ln">3</span><span class="cl">│        ├── 是 → 使用 TaskGroup
</span></span><span class="line"><span class="ln">4</span><span class="cl">│        └── 否 → 是否需要限制並行數量？
</span></span><span class="line"><span class="ln">5</span><span class="cl">│                 ├── 是 → 使用 Semaphore + gather
</span></span><span class="line"><span class="ln">6</span><span class="cl">│                 └── 否 → 使用 gather（可選 return_exceptions）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 否 → 直接使用 await</span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>多個獨立的 I/O 操作（HTTP 請求、檔案讀取、資料庫查詢）</li>
<li>需要等待所有操作完成</li>
<li>操作之間沒有依賴關係</li>
<li>單一操作耗時較長（&gt; 10ms）</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>CPU 密集計算（應用 multiprocessing）</li>
<li>操作之間有依賴關係（應用循序執行或流水線）</li>
<li>單一操作極快（overhead 可能大於收益）</li>
<li>外部服務有嚴格的 rate limit（需要更精細的控制）</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="練習-1用-gather-同時讀取多個設定檔">練習 1：用 gather 同時讀取多個設定檔</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">read_configs</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s2">    並行讀取多個設定檔
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">    提示：使用 aiofiles 或 asyncio.to_thread
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-2實作-worktree-狀態快取">練習 2：實作 worktree 狀態快取</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">WorktreeCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    快取 worktree 狀態，避免頻繁查詢
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    - 使用 dict 儲存狀態
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    - 設定過期時間
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 過期時重新並行查詢
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ttl_seconds</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">30.0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_ttl</span> <span class="o">=</span> <span class="n">ttl_seconds</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_status</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">get_all_statuses</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="練習-3實作帶有重試邏輯的並行下載器">練習 3：實作帶有重試邏輯的並行下載器</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">parallel_download</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">urls</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">bytes</span> <span class="o">|</span> <span class="ne">Exception</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行下載多個 URL，支援重試
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 使用 Semaphore 限制並行數
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 實作指數退避（exponential backoff）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 使用 aiohttp 進行非同步 HTTP 請求
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="練習-4實作-semaphore-限制並行數量的-taskgroup">練習 4：實作 semaphore 限制並行數量的 TaskGroup</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">BoundedTaskGroup</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    限制最大並行數的 TaskGroup
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        async with BoundedTaskGroup(max_concurrent=5) as tg:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">            for item in items:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">                tg.create_task(process(item))
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 結合 Semaphore 和 TaskGroup
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 保持 TaskGroup 的錯誤處理語義
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_concurrent</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_semaphore</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="n">max_concurrent</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_tg</span><span class="p">:</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">create_task</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">coro</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-5實作監控儀表板">練習 5：實作監控儀表板</h4>
<p>建立一個即時監控多個 Git repo 狀態的工具：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">monitor_repos</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">repos</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">interval</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">on_change</span><span class="p">:</span> <span class="n">callable</span> <span class="o">=</span> <span class="kc">None</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 class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    每隔 interval 秒並行檢查所有 repo
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    狀態變化時呼叫 on_change callback
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 使用 asyncio.sleep 控制間隔
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 比較前後狀態找出變化
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 支援 Ctrl+C 優雅退出
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.gather">asyncio.gather 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.TaskGroup">TaskGroup 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Semaphore">Semaphore 官方文件</a></li>
<li><a href="https://peps.python.org/pep-0654/">PEP 654 - Exception Groups</a> - TaskGroup 的異常處理基礎</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">非同步 Subprocess</a></em>
<em>下一章：<a href="/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/" data-link-title="案例：同步/非同步橋接" data-link-desc="用 run_in_executor 和 asyncio.run 在同步與非同步程式碼之間建立橋樑">同步/非同步橋接</a></em></p>
]]></content:encoded></item><item><title>案例：使用 ctypes 呼叫系統 API</title><link>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/ctypes-system-call/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/ctypes-system-call/</guid><description>&lt;p>本案例展示如何使用 ctypes 直接呼叫系統 API，處理 Python 標準庫未提供的底層功能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/ctypes-cffi/" data-link-title="4.1 ctypes 與 cffi：動態綁定" data-link-desc="使用 ctypes 和 cffi 呼叫 C 函式庫">4.1 ctypes 與 cffi：動態綁定&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="為什麼需要直接呼叫系統-api">為什麼需要直接呼叫系統 API？&lt;/h3>
&lt;p>Python 標準庫涵蓋了大多數常見需求，但有時我們需要：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>存取特定系統功能&lt;/strong>：某些底層功能沒有 Python 封裝&lt;/li>
&lt;li>&lt;strong>避免 subprocess 開銷&lt;/strong>：執行外部命令有進程建立的成本&lt;/li>
&lt;li>&lt;strong>即時取得系統資訊&lt;/strong>：某些資訊需要直接從核心取得&lt;/li>
&lt;li>&lt;strong>與 C 函式庫互動&lt;/strong>：使用第三方 C 函式庫的功能&lt;/li>
&lt;/ul>
&lt;h3 id="常見場景">常見場景&lt;/h3>





&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">需要直接呼叫系統 API 的情況：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── 取得主機名稱（gethostname）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── 取得使用者 ID（getuid, geteuid）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── 系統時間操作（time, gettimeofday）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── 檔案系統操作（sync, fsync）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">├── 記憶體資訊（sysinfo - Linux 限定）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">└── 其他未封裝的 POSIX/Windows API&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>雖然許多功能有 Python 對應（如 &lt;code>os.getpid()&lt;/code>、&lt;code>socket.gethostname()&lt;/code>），但理解如何直接呼叫系統 API 是重要的技能：&lt;/p>
&lt;ol>
&lt;li>學習 ctypes 的實際應用&lt;/li>
&lt;li>處理 Python 未封裝的功能&lt;/li>
&lt;li>理解 Python 標準庫的實作原理&lt;/li>
&lt;/ol>
&lt;h2 id="實作方案">實作方案&lt;/h2>
&lt;h3 id="基礎設置跨平台載入-libc">基礎設置：跨平台載入 libc&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># system_api.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">使用 ctypes 呼叫系統 API 的範例模組。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">跨平台支援 Linux、macOS 和 Windows。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes.util&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_libc&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CDLL&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> 跨平台載入 C 函式庫。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> ctypes.CDLL: 載入的 C 函式庫物件
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> OSError: 無法載入 C 函式庫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Windows 使用 msvcrt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CDLL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;msvcrt&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Unix-like 系統使用 find_library&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">libc_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">util&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;c&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">libc_name&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 嘗試常見路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;darwin&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">libc_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;libc.dylib&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">libc_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;libc.so.6&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CDLL&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">libc_name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="c1"># 全域 libc 實例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="n">_libc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CDLL&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_libc&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CDLL&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得 libc 的單例實例。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_libc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_libc&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">_libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_libc&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="範例-1取得主機名稱">範例 1：取得主機名稱&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">socket&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gethostname_ctypes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">256&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 ctypes 呼叫 gethostname() 取得主機名稱。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> max_len: 主機名稱緩衝區大小
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> 主機名稱字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> OSError: 系統呼叫失敗
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># int gethostname(char *name, size_t len)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_char_p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_size_t&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立緩衝區&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_string_buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 呼叫系統函式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gethostname failed with code &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">buffer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="c1"># 比較 ctypes 與 Python 標準庫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ctypes gethostname: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">gethostname_ctypes&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;socket.gethostname: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="範例-2取得-process-id">範例 2：取得 Process ID&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">getpid_ctypes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 ctypes 呼叫 getpid() 取得當前 process ID。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 當前 process 的 PID
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># pid_t getpid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getpid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getpid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getpid&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">getppid_ctypes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 ctypes 呼叫 getppid() 取得父 process ID。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> 父 process 的 PID
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="c1"># pid_t getppid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getppid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getppid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getppid&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ctypes getpid: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">getpid_ctypes&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;os.getpid: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getpid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ctypes getppid: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">getppid_ctypes&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;os.getppid: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getppid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="範例-3unix-時間戳記">範例 3：Unix 時間戳記&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">time_module&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">time_ctypes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 ctypes 呼叫 time() 取得 Unix 時間戳記。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 當前 Unix 時間戳記（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># time_t time(time_t *tloc)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_void_p&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_long&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 傳入 NULL，直接取得回傳值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="c1"># timeval 結構體（用於 gettimeofday）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Timeval&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Structure&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> struct timeval {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> time_t tv_sec; // 秒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> suseconds_t tv_usec; // 微秒
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> };
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">_fields_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;tv_sec&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_long&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;tv_usec&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_long&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gettimeofday_ctypes&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 ctypes 呼叫 gettimeofday() 取得高精度時間。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> (秒, 微秒) 的 tuple
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> Note:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> gettimeofday 在 POSIX.1-2008 中已標記為過時，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建議使用 clock_gettime。此處僅作為教學範例。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">NotImplementedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;gettimeofday not available on Windows&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="c1"># int gettimeofday(struct timeval *tv, struct timezone *tz)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gettimeofday&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">POINTER&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Timeval&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_void_p&lt;/span> &lt;span class="c1"># timezone 已過時，傳 NULL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gettimeofday&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl"> &lt;span class="n">tv&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Timeval&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gettimeofday&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">byref&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tv&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gettimeofday failed with code &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">tv&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tv_sec&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tv&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tv_usec&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ctypes time: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time_ctypes&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;time.time: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">time_module&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="n">sec&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">usec&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gettimeofday_ctypes&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gettimeofday: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">usec&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">06d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="範例-4使用者與群組-idunix-限定">範例 4：使用者與群組 ID（Unix 限定）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_user_ids&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 取得當前 process 的使用者和群組 ID。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 包含 uid, euid, gid, egid 的字典
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> Note:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> 僅支援 Unix-like 系統。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">NotImplementedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;User IDs not applicable on Windows&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 設定函式簽名&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># uid_t getuid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getuid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getuid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_uint&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="c1"># uid_t geteuid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">geteuid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">geteuid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_uint&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="c1"># gid_t getgid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getgid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getgid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_uint&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># gid_t getegid(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getegid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getegid&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_uint&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;uid&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getuid&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;euid&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">geteuid&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;gid&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getgid&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;egid&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getegid&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user_ids&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ctypes: uid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;uid&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, euid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;euid&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;gid&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, egid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ids&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;egid&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;os: uid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getuid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, euid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">geteuid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getgid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, egid=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getegid&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="跨平台考量">跨平台考量&lt;/h2>
&lt;h3 id="平台差異對照表">平台差異對照表&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>Linux&lt;/th>
 &lt;th>macOS&lt;/th>
 &lt;th>Windows&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>libc 名稱&lt;/td>
 &lt;td>&lt;code>libc.so.6&lt;/code>&lt;/td>
 &lt;td>&lt;code>libc.dylib&lt;/code>&lt;/td>
 &lt;td>&lt;code>msvcrt&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>gethostname&lt;/code>&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>&lt;code>kernel32.GetComputerNameA&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>getpid&lt;/code>&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>&lt;code>kernel32.GetCurrentProcessId&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>time&lt;/code>&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>&lt;code>msvcrt.time&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>getuid/geteuid&lt;/code>&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>libc&lt;/td>
 &lt;td>不適用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="windows-特定實作">Windows 特定實作&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gethostname_windows&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">256&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> Windows 版本的 gethostname。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 kernel32.GetComputerNameA。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&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="k">raise&lt;/span> &lt;span class="ne">NotImplementedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This function is Windows-only&amp;#34;&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">kernel32&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">windll&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kernel32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD nSize)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_string_buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_ulong&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">kernel32&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GetComputerNameA&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">byref&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;GetComputerNameA failed&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">buffer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">getpid_windows&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2"> Windows 版本的 getpid。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 kernel32.GetCurrentProcessId。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">NotImplementedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This function is Windows-only&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">kernel32&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">windll&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kernel32&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># DWORD GetCurrentProcessId(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">kernel32&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GetCurrentProcessId&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">kernel32&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GetCurrentProcessId&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_ulong&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">kernel32&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GetCurrentProcessId&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="跨平台封裝">跨平台封裝&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_hostname&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;跨平台取得主機名稱。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&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 class="k">return&lt;/span> &lt;span class="n">gethostname_windows&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">gethostname_ctypes&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_process_id&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="s2">&amp;#34;&amp;#34;&amp;#34;跨平台取得 process ID。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">platform&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;win32&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">getpid_windows&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">getpid_ctypes&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="錯誤處理與安全性">錯誤處理與安全性&lt;/h2>
&lt;h3 id="常見錯誤類型">常見錯誤類型&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">errno&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">safe_gethostname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">256&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> 安全版本的 gethostname，包含完整的錯誤處理。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 設定函式簽名&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_char_p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_size_t&lt;/span>&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="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證參數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_len&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;max_len must be positive&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_len&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;max_len too large (max 1024)&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立緩衝區&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">buffer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_string_buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 呼叫系統函式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_len&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 取得錯誤碼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">err&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_errno&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ENAMETOOLONG&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ENAMETOOLONG&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Hostname too long for buffer&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">err&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EFAULT&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EFAULT&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Invalid buffer address&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">OSError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;gethostname failed: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errorcode&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;Unknown&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 解碼並處理可能的編碼錯誤&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">buffer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">UnicodeDecodeError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">buffer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;latin-1&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="安全性考量">安全性考量&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">使用 ctypes 時的安全性注意事項：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">1. 緩衝區溢位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 永遠確保緩衝區大小足夠
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 使用 create_string_buffer() 而非直接操作指標
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">2. 型別安全
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 務必設定 argtypes 和 restype
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 錯誤的型別可能導致程式崩潰或安全漏洞
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2">3. 記憶體管理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> - ctypes 物件由 Python GC 管理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 小心回呼函式的生命週期
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2">4. 輸入驗證
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 永遠驗證使用者輸入
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 不要直接將未驗證的資料傳給 C 函式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">secure_strlen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 安全的 strlen 範例，包含輸入驗證。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 輸入驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">TypeError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Expected str, got {type(s).__name__}&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 限制長度避免 DoS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">MAX_LENGTH&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10_000_000&lt;/span> &lt;span class="c1"># 10 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">MAX_LENGTH&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;String too long (max &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">MAX_LENGTH&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes)&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strlen&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_char_p&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strlen&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_size_t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 轉換為 bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">encoded&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strlen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoded&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="錯誤碼處理">錯誤碼處理&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">errno&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_errno_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;取得錯誤碼對應的訊息。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">libc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_libc&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># char *strerror(int errnum)&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">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strerror&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">argtypes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_int&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">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strerror&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">restype&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ctypes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">c_char_p&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">libc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strerror&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Unknown error &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">err&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用範例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ENOENT (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ENOENT&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">get_errno_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ENOENT&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;EACCES (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EACCES&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">get_errno_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EACCES&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;EINVAL (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EINVAL&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">get_errno_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">errno&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">EINVAL&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="效能比較ctypes-vs-subprocess">效能比較：ctypes vs subprocess&lt;/h2>
&lt;h3 id="測試腳本">測試腳本&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">statistics&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Callable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;執行效能測試並回傳統計資料。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">times&lt;/span> &lt;span class="o">=&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 暖機&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&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="n">func&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 實際測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">times&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;mean&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">statistics&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1_000_000&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">23&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;stdev&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">statistics&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdev&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1_000_000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;min&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">min&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1_000_000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;max&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">max&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">1_000_000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="c1"># 方法 1：ctypes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hostname_ctypes&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">gethostname_ctypes&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="c1"># 方法 2：subprocess&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hostname_subprocess&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;hostname&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">capture_output&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="c1"># 方法 3：Python 標準庫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hostname_stdlib&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">socket&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">socket&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gethostname&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;取得主機名稱效能比較&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">methods&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;ctypes&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hostname_ctypes&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;subprocess&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hostname_subprocess&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;socket (stdlib)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hostname_stdlib&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">func&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">methods&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">benchmark&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">:&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 平均: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;mean&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> us&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 標準差: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;stdev&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> us&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 最小: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;min&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> us&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; 最大: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;max&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> us&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="效能測試結果">效能測試結果&lt;/h3>





&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">============================================================
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">取得主機名稱效能比較
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">============================================================
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">ctypes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> 平均: 1.52 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> 標準差: 0.31 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> 最小: 1.21 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> 最大: 8.45 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">subprocess:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> 平均: 4523.67 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> 標準差: 892.34 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> 最小: 3128.45 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> 最大: 12456.78 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">socket (stdlib):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> 平均: 0.89 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> 標準差: 0.18 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> 最小: 0.72 us
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> 最大: 4.23 us&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="結果分析">結果分析&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>方法&lt;/th>
 &lt;th>平均時間&lt;/th>
 &lt;th>相對 ctypes&lt;/th>
 &lt;th>適用場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>socket (stdlib)&lt;/strong>&lt;/td>
 &lt;td>~0.9 us&lt;/td>
 &lt;td>0.6x (最快)&lt;/td>
 &lt;td>首選，已有封裝&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>ctypes&lt;/strong>&lt;/td>
 &lt;td>~1.5 us&lt;/td>
 &lt;td>1x (基準)&lt;/td>
 &lt;td>無標準庫支援時&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>subprocess&lt;/strong>&lt;/td>
 &lt;td>~4500 us&lt;/td>
 &lt;td>~3000x (最慢)&lt;/td>
 &lt;td>需要執行外部命令時&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>結論&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例展示如何使用 ctypes 直接呼叫系統 API，處理 Python 標準庫未提供的底層功能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/05-c-extensions/ctypes-cffi/" data-link-title="4.1 ctypes 與 cffi：動態綁定" data-link-desc="使用 ctypes 和 cffi 呼叫 C 函式庫">4.1 ctypes 與 cffi：動態綁定</a></li>
<li><a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="為什麼需要直接呼叫系統-api">為什麼需要直接呼叫系統 API？</h3>
<p>Python 標準庫涵蓋了大多數常見需求，但有時我們需要：</p>
<ul>
<li><strong>存取特定系統功能</strong>：某些底層功能沒有 Python 封裝</li>
<li><strong>避免 subprocess 開銷</strong>：執行外部命令有進程建立的成本</li>
<li><strong>即時取得系統資訊</strong>：某些資訊需要直接從核心取得</li>
<li><strong>與 C 函式庫互動</strong>：使用第三方 C 函式庫的功能</li>
</ul>
<h3 id="常見場景">常見場景</h3>





<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">需要直接呼叫系統 API 的情況：
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 取得主機名稱（gethostname）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── 取得使用者 ID（getuid, geteuid）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── 系統時間操作（time, gettimeofday）
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── 檔案系統操作（sync, fsync）
</span></span><span class="line"><span class="ln">6</span><span class="cl">├── 記憶體資訊（sysinfo - Linux 限定）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 其他未封裝的 POSIX/Windows API</span></span></code></pre></div><p>雖然許多功能有 Python 對應（如 <code>os.getpid()</code>、<code>socket.gethostname()</code>），但理解如何直接呼叫系統 API 是重要的技能：</p>
<ol>
<li>學習 ctypes 的實際應用</li>
<li>處理 Python 未封裝的功能</li>
<li>理解 Python 標準庫的實作原理</li>
</ol>
<h2 id="實作方案">實作方案</h2>
<h3 id="基礎設置跨平台載入-libc">基礎設置：跨平台載入 libc</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># system_api.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">使用 ctypes 呼叫系統 API 的範例模組。
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">跨平台支援 Linux、macOS 和 Windows。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes.util</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">load_libc</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">CDLL</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    跨平台載入 C 函式庫。
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        ctypes.CDLL: 載入的 C 函式庫物件
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    Raises:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        OSError: 無法載入 C 函式庫
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># Windows 使用 msvcrt</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">return</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">CDLL</span><span class="p">(</span><span class="s1">&#39;msvcrt&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Unix-like 系統使用 find_library</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">libc_name</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">find_library</span><span class="p">(</span><span class="s1">&#39;c&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">if</span> <span class="n">libc_name</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="c1"># 嘗試常見路徑</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;darwin&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">libc_name</span> <span class="o">=</span> <span class="s1">&#39;libc.dylib&#39;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">libc_name</span> <span class="o">=</span> <span class="s1">&#39;libc.so.6&#39;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">CDLL</span><span class="p">(</span><span class="n">libc_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"># 全域 libc 實例</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="n">_libc</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">CDLL</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">get_libc</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">CDLL</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;取得 libc 的單例實例。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">global</span> <span class="n">_libc</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">if</span> <span class="n">_libc</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">_libc</span> <span class="o">=</span> <span class="n">load_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="n">_libc</span></span></span></code></pre></div><h3 id="範例-1取得主機名稱">範例 1：取得主機名稱</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">gethostname_ctypes</span><span class="p">(</span><span class="n">max_len</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">256</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    使用 ctypes 呼叫 gethostname() 取得主機名稱。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        max_len: 主機名稱緩衝區大小
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        主機名稱字串
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Raises:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        OSError: 系統呼叫失敗
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># int gethostname(char *name, size_t len)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span><span class="p">,</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_size_t</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># 建立緩衝區</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">buffer</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">create_string_buffer</span><span class="p">(</span><span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 呼叫系統函式</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">if</span> <span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;gethostname failed with code </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="n">buffer</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># 比較 ctypes 與 Python 標準庫</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ctypes gethostname: </span><span class="si">{</span><span class="n">gethostname_ctypes</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;socket.gethostname: </span><span class="si">{</span><span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="範例-2取得-process-id">範例 2：取得 Process ID</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">getpid_ctypes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    使用 ctypes 呼叫 getpid() 取得當前 process ID。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        當前 process 的 PID
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># pid_t getpid(void)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getpid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getpid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">libc</span><span class="o">.</span><span class="n">getpid</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">getppid_ctypes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    使用 ctypes 呼叫 getppid() 取得父 process ID。
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        父 process 的 PID
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># pid_t getppid(void)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getppid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getppid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="n">libc</span><span class="o">.</span><span class="n">getppid</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># 驗證結果</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ctypes getpid: </span><span class="si">{</span><span class="n">getpid_ctypes</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;os.getpid: </span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getpid</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ctypes getppid: </span><span class="si">{</span><span class="n">getppid_ctypes</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;os.getppid: </span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getppid</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="範例-3unix-時間戳記">範例 3：Unix 時間戳記</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span> <span class="k">as</span> <span class="nn">time_module</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">time_ctypes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    使用 ctypes 呼叫 time() 取得 Unix 時間戳記。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        當前 Unix 時間戳記（秒）
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># time_t time(time_t *tloc)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">time</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_void_p</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">time</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_long</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># 傳入 NULL，直接取得回傳值</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">libc</span><span class="o">.</span><span class="n">time</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># timeval 結構體（用於 gettimeofday）</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">Timeval</span><span class="p">(</span><span class="n">ctypes</span><span class="o">.</span><span class="n">Structure</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    struct timeval {
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        time_t      tv_sec;   // 秒
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        suseconds_t tv_usec;  // 微秒
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    };
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">_fields_</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;tv_sec&#34;</span><span class="p">,</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_long</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;tv_usec&#34;</span><span class="p">,</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_long</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">gettimeofday_ctypes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    使用 ctypes 呼叫 gettimeofday() 取得高精度時間。
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        (秒, 微秒) 的 tuple
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        gettimeofday 在 POSIX.1-2008 中已標記為過時，
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">        建議使用 clock_gettime。此處僅作為教學範例。
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="s2">&#34;gettimeofday not available on Windows&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># int gettimeofday(struct timeval *tv, struct timezone *tz)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gettimeofday</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="n">ctypes</span><span class="o">.</span><span class="n">POINTER</span><span class="p">(</span><span class="n">Timeval</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">ctypes</span><span class="o">.</span><span class="n">c_void_p</span>  <span class="c1"># timezone 已過時，傳 NULL</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gettimeofday</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="n">tv</span> <span class="o">=</span> <span class="n">Timeval</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">libc</span><span class="o">.</span><span class="n">gettimeofday</span><span class="p">(</span><span class="n">ctypes</span><span class="o">.</span><span class="n">byref</span><span class="p">(</span><span class="n">tv</span><span class="p">),</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="k">if</span> <span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;gettimeofday failed with code </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">tv</span><span class="o">.</span><span class="n">tv_sec</span><span class="p">,</span> <span class="n">tv</span><span class="o">.</span><span class="n">tv_usec</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="c1"># 驗證結果</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ctypes time: </span><span class="si">{</span><span class="n">time_ctypes</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;time.time: </span><span class="si">{</span><span class="nb">int</span><span class="p">(</span><span class="n">time_module</span><span class="o">.</span><span class="n">time</span><span class="p">())</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">!=</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="n">sec</span><span class="p">,</span> <span class="n">usec</span> <span class="o">=</span> <span class="n">gettimeofday_ctypes</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;gettimeofday: </span><span class="si">{</span><span class="n">sec</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="n">usec</span><span class="si">:</span><span class="s2">06d</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="範例-4使用者與群組-idunix-限定">範例 4：使用者與群組 ID（Unix 限定）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">get_user_ids</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    取得當前 process 的使用者和群組 ID。
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        包含 uid, euid, gid, egid 的字典
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        僅支援 Unix-like 系統。
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="s2">&#34;User IDs not applicable on Windows&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="c1"># 設定函式簽名</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># uid_t getuid(void)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getuid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getuid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_uint</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># uid_t geteuid(void)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">geteuid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">geteuid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_uint</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># gid_t getgid(void)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getgid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getgid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_uint</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># gid_t getegid(void)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getegid</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getegid</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_uint</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="s1">&#39;uid&#39;</span><span class="p">:</span> <span class="n">libc</span><span class="o">.</span><span class="n">getuid</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s1">&#39;euid&#39;</span><span class="p">:</span> <span class="n">libc</span><span class="o">.</span><span class="n">geteuid</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="s1">&#39;gid&#39;</span><span class="p">:</span> <span class="n">libc</span><span class="o">.</span><span class="n">getgid</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="s1">&#39;egid&#39;</span><span class="p">:</span> <span class="n">libc</span><span class="o">.</span><span class="n">getegid</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># 驗證結果</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">!=</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">ids</span> <span class="o">=</span> <span class="n">get_user_ids</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ctypes: uid=</span><span class="si">{</span><span class="n">ids</span><span class="p">[</span><span class="s1">&#39;uid&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, euid=</span><span class="si">{</span><span class="n">ids</span><span class="p">[</span><span class="s1">&#39;euid&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;gid=</span><span class="si">{</span><span class="n">ids</span><span class="p">[</span><span class="s1">&#39;gid&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, egid=</span><span class="si">{</span><span class="n">ids</span><span class="p">[</span><span class="s1">&#39;egid&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;os: uid=</span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getuid</span><span class="p">()</span><span class="si">}</span><span class="s2">, euid=</span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">geteuid</span><span class="p">()</span><span class="si">}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;gid=</span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getgid</span><span class="p">()</span><span class="si">}</span><span class="s2">, egid=</span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getegid</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="跨平台考量">跨平台考量</h2>
<h3 id="平台差異對照表">平台差異對照表</h3>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>Linux</th>
          <th>macOS</th>
          <th>Windows</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>libc 名稱</td>
          <td><code>libc.so.6</code></td>
          <td><code>libc.dylib</code></td>
          <td><code>msvcrt</code></td>
      </tr>
      <tr>
          <td><code>gethostname</code></td>
          <td>libc</td>
          <td>libc</td>
          <td><code>kernel32.GetComputerNameA</code></td>
      </tr>
      <tr>
          <td><code>getpid</code></td>
          <td>libc</td>
          <td>libc</td>
          <td><code>kernel32.GetCurrentProcessId</code></td>
      </tr>
      <tr>
          <td><code>time</code></td>
          <td>libc</td>
          <td>libc</td>
          <td><code>msvcrt.time</code></td>
      </tr>
      <tr>
          <td><code>getuid/geteuid</code></td>
          <td>libc</td>
          <td>libc</td>
          <td>不適用</td>
      </tr>
  </tbody>
</table>
<h3 id="windows-特定實作">Windows 特定實作</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">gethostname_windows</span><span class="p">(</span><span class="n">max_len</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">256</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    Windows 版本的 gethostname。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    使用 kernel32.GetComputerNameA。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">!=</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="s2">&#34;This function is Windows-only&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">kernel32</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">windll</span><span class="o">.</span><span class="n">kernel32</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># BOOL GetComputerNameA(LPSTR lpBuffer, LPDWORD nSize)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">buffer</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">create_string_buffer</span><span class="p">(</span><span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">size</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_ulong</span><span class="p">(</span><span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">kernel32</span><span class="o">.</span><span class="n">GetComputerNameA</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">byref</span><span class="p">(</span><span class="n">size</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;GetComputerNameA failed&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">return</span> <span class="n">buffer</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">def</span> <span class="nf">getpid_windows</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    Windows 版本的 getpid。
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">    使用 kernel32.GetCurrentProcessId。
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">!=</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span><span class="s2">&#34;This function is Windows-only&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">kernel32</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">windll</span><span class="o">.</span><span class="n">kernel32</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="c1"># DWORD GetCurrentProcessId(void)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">kernel32</span><span class="o">.</span><span class="n">GetCurrentProcessId</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">kernel32</span><span class="o">.</span><span class="n">GetCurrentProcessId</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_ulong</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="k">return</span> <span class="n">kernel32</span><span class="o">.</span><span class="n">GetCurrentProcessId</span><span class="p">()</span></span></span></code></pre></div><h3 id="跨平台封裝">跨平台封裝</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">get_hostname</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;跨平台取得主機名稱。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="n">gethostname_windows</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">gethostname_ctypes</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">get_process_id</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;跨平台取得 process ID。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">platform</span> <span class="o">==</span> <span class="s1">&#39;win32&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">getpid_windows</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="n">getpid_ctypes</span><span class="p">()</span></span></span></code></pre></div><h2 id="錯誤處理與安全性">錯誤處理與安全性</h2>
<h3 id="常見錯誤類型">常見錯誤類型</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">errno</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">safe_gethostname</span><span class="p">(</span><span class="n">max_len</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">256</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    安全版本的 gethostname，包含完整的錯誤處理。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 設定函式簽名</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span><span class="p">,</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_size_t</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 驗證參數</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="n">max_len</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;max_len must be positive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="n">max_len</span> <span class="o">&gt;</span> <span class="mi">1024</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;max_len too large (max 1024)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 建立緩衝區</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">buffer</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">create_string_buffer</span><span class="p">(</span><span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="c1"># 呼叫系統函式</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">libc</span><span class="o">.</span><span class="n">gethostname</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">max_len</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">if</span> <span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="c1"># 取得錯誤碼</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">err</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">get_errno</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="n">errno</span><span class="o">.</span><span class="n">ENAMETOOLONG</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">ENAMETOOLONG</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                         <span class="s2">&#34;Hostname too long for buffer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">elif</span> <span class="n">err</span> <span class="o">==</span> <span class="n">errno</span><span class="o">.</span><span class="n">EFAULT</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EFAULT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                         <span class="s2">&#34;Invalid buffer address&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;gethostname failed: </span><span class="si">{</span><span class="n">errno</span><span class="o">.</span><span class="n">errorcode</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">err</span><span class="p">,</span> <span class="s1">&#39;Unknown&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="c1"># 解碼並處理可能的編碼錯誤</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">except</span> <span class="ne">UnicodeDecodeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;latin-1&#39;</span><span class="p">)</span></span></span></code></pre></div><h3 id="安全性考量">安全性考量</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">使用 ctypes 時的安全性注意事項：
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">1. 緩衝區溢位
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">   - 永遠確保緩衝區大小足夠
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">   - 使用 create_string_buffer() 而非直接操作指標
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">2. 型別安全
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">   - 務必設定 argtypes 和 restype
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">   - 錯誤的型別可能導致程式崩潰或安全漏洞
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">3. 記憶體管理
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">   - ctypes 物件由 Python GC 管理
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">   - 小心回呼函式的生命週期
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">4. 輸入驗證
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">   - 永遠驗證使用者輸入
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">   - 不要直接將未驗證的資料傳給 C 函式
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">def</span> <span class="nf">secure_strlen</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    安全的 strlen 範例，包含輸入驗證。
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 輸入驗證</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s2">&#34;Expected str, got {type(s).__name__}&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># 限制長度避免 DoS</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">MAX_LENGTH</span> <span class="o">=</span> <span class="mi">10_000_000</span>  <span class="c1"># 10 MB</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">MAX_LENGTH</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;String too long (max </span><span class="si">{</span><span class="n">MAX_LENGTH</span><span class="si">}</span><span class="s2"> bytes)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">strlen</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">strlen</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_size_t</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># 轉換為 bytes</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">encoded</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="k">return</span> <span class="n">libc</span><span class="o">.</span><span class="n">strlen</span><span class="p">(</span><span class="n">encoded</span><span class="p">)</span></span></span></code></pre></div><h3 id="錯誤碼處理">錯誤碼處理</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">ctypes</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">errno</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">get_errno_message</span><span class="p">(</span><span class="n">err</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;取得錯誤碼對應的訊息。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># char *strerror(int errnum)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">strerror</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_int</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">strerror</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">libc</span><span class="o">.</span><span class="n">strerror</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Unknown error </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ENOENT (</span><span class="si">{</span><span class="n">errno</span><span class="o">.</span><span class="n">ENOENT</span><span class="si">}</span><span class="s2">): </span><span class="si">{</span><span class="n">get_errno_message</span><span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">ENOENT</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;EACCES (</span><span class="si">{</span><span class="n">errno</span><span class="o">.</span><span class="n">EACCES</span><span class="si">}</span><span class="s2">): </span><span class="si">{</span><span class="n">get_errno_message</span><span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EACCES</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;EINVAL (</span><span class="si">{</span><span class="n">errno</span><span class="o">.</span><span class="n">EINVAL</span><span class="si">}</span><span class="s2">): </span><span class="si">{</span><span class="n">get_errno_message</span><span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EINVAL</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="效能比較ctypes-vs-subprocess">效能比較：ctypes vs subprocess</h2>
<h3 id="測試腳本">測試腳本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">statistics</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行效能測試並回傳統計資料。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 暖機</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 實際測試</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s1">&#39;mean&#39;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span><span class="p">,</span>  <span class="c1"># 轉換為微秒</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s1">&#39;stdev&#39;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">stdev</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="s1">&#39;min&#39;</span><span class="p">:</span> <span class="nb">min</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s1">&#39;max&#39;</span><span class="p">:</span> <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 方法 1：ctypes</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">hostname_ctypes</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="n">gethostname_ctypes</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># 方法 2：subprocess</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">hostname_subprocess</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">[</span><span class="s1">&#39;hostname&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">capture_output</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">text</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"># 方法 3：Python 標準庫</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">def</span> <span class="nf">hostname_stdlib</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">return</span> <span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"># 執行測試</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;取得主機名稱效能比較&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="n">methods</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;ctypes&#34;</span><span class="p">,</span> <span class="n">hostname_ctypes</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;subprocess&#34;</span><span class="p">,</span> <span class="n">hostname_subprocess</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;socket (stdlib)&#34;</span><span class="p">,</span> <span class="n">hostname_stdlib</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">func</span> <span class="ow">in</span> <span class="n">methods</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  平均: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  標準差: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;stdev&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  最小: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;min&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  最大: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;max&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> us&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能測試結果">效能測試結果</h3>





<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">============================================================
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">取得主機名稱效能比較
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">ctypes:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  平均: 1.52 us
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  標準差: 0.31 us
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  最小: 1.21 us
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  最大: 8.45 us
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">subprocess:
</span></span><span class="line"><span class="ln">12</span><span class="cl">  平均: 4523.67 us
</span></span><span class="line"><span class="ln">13</span><span class="cl">  標準差: 892.34 us
</span></span><span class="line"><span class="ln">14</span><span class="cl">  最小: 3128.45 us
</span></span><span class="line"><span class="ln">15</span><span class="cl">  最大: 12456.78 us
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">socket (stdlib):
</span></span><span class="line"><span class="ln">18</span><span class="cl">  平均: 0.89 us
</span></span><span class="line"><span class="ln">19</span><span class="cl">  標準差: 0.18 us
</span></span><span class="line"><span class="ln">20</span><span class="cl">  最小: 0.72 us
</span></span><span class="line"><span class="ln">21</span><span class="cl">  最大: 4.23 us</span></span></code></pre></div><h3 id="結果分析">結果分析</h3>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>平均時間</th>
          <th>相對 ctypes</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>socket (stdlib)</strong></td>
          <td>~0.9 us</td>
          <td>0.6x (最快)</td>
          <td>首選，已有封裝</td>
      </tr>
      <tr>
          <td><strong>ctypes</strong></td>
          <td>~1.5 us</td>
          <td>1x (基準)</td>
          <td>無標準庫支援時</td>
      </tr>
      <tr>
          <td><strong>subprocess</strong></td>
          <td>~4500 us</td>
          <td>~3000x (最慢)</td>
          <td>需要執行外部命令時</td>
      </tr>
  </tbody>
</table>
<p><strong>結論</strong>：</p>
<ol>
<li><strong>優先使用標準庫</strong>：如果 Python 標準庫有對應功能，通常是最佳選擇</li>
<li><strong>ctypes 是好的替代方案</strong>：效能接近標準庫，適合未封裝的系統 API</li>
<li><strong>避免 subprocess 取得簡單資訊</strong>：進程建立開銷約 3000 倍</li>
</ol>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>ctypes</th>
          <th>subprocess</th>
          <th>標準庫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>效能</strong></td>
          <td>優秀 (~1-5 us)</td>
          <td>差 (~3-5 ms)</td>
          <td>最佳 (~1 us)</td>
      </tr>
      <tr>
          <td><strong>可移植性</strong></td>
          <td>需處理平台差異</td>
          <td>取決於命令可用性</td>
          <td>優秀</td>
      </tr>
      <tr>
          <td><strong>複雜度</strong></td>
          <td>中（需了解 C 型別）</td>
          <td>低</td>
          <td>低</td>
      </tr>
      <tr>
          <td><strong>安全性</strong></td>
          <td>需謹慎處理</td>
          <td>需防止命令注入</td>
          <td>良好</td>
      </tr>
      <tr>
          <td><strong>功能範圍</strong></td>
          <td>廣（任何 C 函式）</td>
          <td>廣（任何命令）</td>
          <td>受限於已實作功能</td>
      </tr>
  </tbody>
</table>
<h2 id="實際應用建議">實際應用建議</h2>
<h3 id="何時使用-ctypes">何時使用 ctypes</h3>





<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">適合使用 ctypes 的情況：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── Python 標準庫沒有對應功能
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── 需要呼叫特定平台的 API
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 效能是關鍵考量
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── 需要與 C 函式庫整合
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">└── 希望避免編譯步驟（相比 Cython）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">不建議使用 ctypes 的情況：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── 標準庫已有對應功能
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 大量複雜的 C 介面（考慮 cffi 或 Cython）
</span></span><span class="line"><span class="ln">11</span><span class="cl">├── 需要頻繁傳遞大量資料（考慮 NumPy）
</span></span><span class="line"><span class="ln">12</span><span class="cl">└── 團隊不熟悉 C 語言</span></span></code></pre></div><h3 id="最佳實踐">最佳實踐</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">ctypes 呼叫系統 API 的最佳實踐：
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">1. 封裝成模組
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">   - 將 ctypes 呼叫封裝在獨立模組中
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">   - 提供清晰的 Python API
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">2. 完整的型別宣告
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">   - 永遠設定 argtypes 和 restype
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">   - 使用適當的 ctypes 型別
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">3. 錯誤處理
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">   - 檢查回傳值
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">   - 處理 errno
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">   - 提供有意義的錯誤訊息
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">4. 跨平台支援
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">   - 使用 ctypes.util.find_library()
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">   - 提供平台特定的實作
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">   - 考慮使用 Python 標準庫作為 fallback
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">5. 文件與測試
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">   - 記錄 C 函式的原型
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">   - 與 Python 標準庫比較結果
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">   - 包含效能測試
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<p>使用 ctypes 實作以下功能：</p>
<ol>
<li><strong>取得環境變數</strong>：呼叫 <code>getenv()</code> 函式</li>
<li><strong>設定環境變數</strong>：呼叫 <code>setenv()</code> 或 <code>putenv()</code> 函式（Unix）</li>
<li><strong>取得當前工作目錄</strong>：呼叫 <code>getcwd()</code> 函式</li>
</ol>
<p>提示：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># getenv 範例框架</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">getenv_ctypes</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 ctypes 取得環境變數。&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">libc</span> <span class="o">=</span> <span class="n">get_libc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># char *getenv(const char *name)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getenv</span><span class="o">.</span><span class="n">argtypes</span> <span class="o">=</span> <span class="p">[</span><span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">libc</span><span class="o">.</span><span class="n">getenv</span><span class="o">.</span><span class="n">restype</span> <span class="o">=</span> <span class="n">ctypes</span><span class="o">.</span><span class="n">c_char_p</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">libc</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># 完成實作...</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<ol>
<li>
<p>實作一個跨平台的系統資訊模組，包含：</p>
<ul>
<li>主機名稱</li>
<li>Process ID</li>
<li>使用者 ID（Unix）/ 使用者名稱（Windows）</li>
<li>系統時間</li>
</ul>
</li>
<li>
<p>比較你的實作與 <code>psutil</code> 套件的效能差異。</p>
</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/ctypes.html">Python ctypes 官方文件</a></li>
<li><a href="https://man7.org/linux/man-pages/dir_section_2.html">Linux man pages - section 2 (system calls)</a></li>
<li><a href="https://docs.microsoft.com/en-us/windows/win32/api/">Windows API Reference</a></li>
<li><a href="https://pubs.opengroup.org/onlinepubs/9699919799/">POSIX 標準</a></li>
</ul>
<hr>
<p><em>返回：<a href="/blog/python-advanced/05-c-extensions/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的 C 擴展案例">案例研究</a></em>
<em>返回：<a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python</a></em></p>
]]></content:encoded></item><item><title>案例：使用 Poetry 完整工作流</title><link>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/poetry-workflow/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/poetry-workflow/</guid><description>&lt;p>本案例展示如何使用 Poetry 管理現代 Python 專案的完整生命週期，從專案建立到套件發布。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/build-systems/" data-link-title="6.2 建構系統比較" data-link-desc="比較 setuptools、Poetry、Hatch 等建構系統">建構系統比較&lt;/a>&lt;/li>
&lt;li>Python 虛擬環境基礎&lt;/li>
&lt;li>套件相依性概念&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現代專案的挑戰">現代專案的挑戰&lt;/h3>
&lt;p>傳統的 Python 專案管理面臨以下問題：&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">傳統工作流程的痛點：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── 相依性管理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ ├── requirements.txt 無法鎖定間接相依性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ ├── pip freeze 產生的版本可能過於嚴格
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ └── 開發與生產環境相依性混在一起
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ ├── 不同專案的環境容易混淆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ └── 缺乏與專案的關聯性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">├── 建構與發布
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ ├── setup.py 設定複雜
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ ├── 發布流程需要多個工具
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ └── 版本管理不一致
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">└── 團隊協作
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> ├── 環境難以重現
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> ├── 「在我電腦上可以跑」問題
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> └── 新成員上手困難&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼選擇-poetry">為什麼選擇 Poetry？&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>特性&lt;/th>
 &lt;th>pip + venv&lt;/th>
 &lt;th>Poetry&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>相依性鎖定&lt;/td>
 &lt;td>手動（pip freeze）&lt;/td>
 &lt;td>自動（poetry.lock）&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>需要 twine&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;h2 id="解決方案">解決方案&lt;/h2>
&lt;h3 id="安裝-poetry">安裝 Poetry&lt;/h3>





&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">&lt;span class="c1"># 官方推薦的安裝方式（獨立安裝）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">curl -sSL https://install.python-poetry.org &lt;span class="p">|&lt;/span> python3 -
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或使用 pipx（推薦用於 CLI 工具）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">pipx install poetry
&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"># 確認安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">poetry --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="設定-poetry">設定 Poetry&lt;/h4>





&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">&lt;span class="c1"># 在專案目錄中建立虛擬環境（推薦）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">poetry config virtualenvs.in-project &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查看所有設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">poetry config --list&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="工作流一建立新專案">工作流一：建立新專案&lt;/h3>
&lt;h4 id="使用-poetry-new完整專案結構">使用 poetry new（完整專案結構）&lt;/h4>





&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">&lt;span class="c1"># 建立新專案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry new my-awesome-lib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 產生的目錄結構&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">my-awesome-lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── pyproject.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── my_awesome_lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ └── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> └── __init__.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">&lt;span class="c1"># 使用 src layout（推薦用於函式庫）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry new --src my-awesome-lib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 產生的目錄結構&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">my-awesome-lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── pyproject.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ └── my_awesome_lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ └── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> └── __init__.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="使用-poetry-init現有專案">使用 poetry init（現有專案）&lt;/h4>





&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">&lt;span class="c1"># 在現有目錄初始化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> existing-project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">poetry init
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 互動式問答&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># - Package name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># - Version&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"># - Description&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># - Author&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># - License&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># - Python version&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># - Dependencies&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="互動式初始化範例">互動式初始化範例&lt;/h5>





&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">This command will guide you through creating your pyproject.toml config.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">Package name [existing-project]: my-package
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">Version [0.1.0]: 1.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">Description []: A useful Python package
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">Author [Your Name &amp;lt;you@example.com&amp;gt;, n to skip]:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">License []: MIT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">Compatible Python versions [^3.10]: ^3.10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">Would you like to define your main dependencies interactively? (yes/no) [yes]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">Search for package to add (or leave blank to continue): requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="工作流二管理相依性">工作流二：管理相依性&lt;/h3>
&lt;h4 id="新增相依性">新增相依性&lt;/h4>





&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">&lt;span class="c1"># 新增生產相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry add requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">poetry add &lt;span class="s2">&amp;#34;httpx&amp;gt;=0.24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 新增開發相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">poetry add pytest --group dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">poetry add ruff mypy --group dev
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 新增文件相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">poetry add mkdocs mkdocs-material --group docs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 新增可選相依性（extras）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">poetry add pyyaml --optional&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="pyprojecttoml-的變化">pyproject.toml 的變化&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="nx">python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">requests&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^2.31.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nx">httpx&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;=0.24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">pytest&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^8.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">ruff&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^0.4.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">mypy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^1.10.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&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 class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&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="nx">mkdocs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^1.5.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nx">mkdocs-material&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^9.5.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">extras&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">yaml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pyyaml&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="移除相依性">移除相依性&lt;/h4>





&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">&lt;span class="c1"># 移除生產相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">poetry remove requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 移除開發相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">poetry remove pytest --group dev&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="更新相依性">更新相依性&lt;/h4>





&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">&lt;span class="c1"># 更新所有相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 更新特定套件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry update requests
&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"># 檢視可更新的套件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">poetry show --outdated
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 檢視相依性樹&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">poetry show --tree&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="相依性樹範例輸出">相依性樹範例輸出&lt;/h5>





&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">requests 2.31.0 Python HTTP for Humans.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── certifi &amp;gt;=2017.4.17
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── charset-normalizer &amp;gt;=2,&amp;lt;4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── idna &amp;gt;=2.5,&amp;lt;4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── urllib3 &amp;gt;=1.21.1,&amp;lt;3&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="工作流三虛擬環境管理">工作流三：虛擬環境管理&lt;/h3>
&lt;h4 id="自動環境管理">自動環境管理&lt;/h4>





&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">&lt;span class="c1"># 安裝所有相依性（自動建立虛擬環境）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 僅安裝生產相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry install --only main
&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"># 安裝包含特定群組&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">poetry install --with dev,docs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 排除特定群組&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">poetry install --without docs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 安裝 extras&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">poetry install --extras yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">poetry install --all-extras&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="環境操作">環境操作&lt;/h4>





&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">&lt;span class="c1"># 進入虛擬環境 shell&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry shell
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在虛擬環境中執行命令（不進入 shell）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry run python script.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">poetry run pytest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">poetry run python -c &lt;span class="s2">&amp;#34;import my_package; print(my_package.__version__)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 顯示環境資訊&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">poetry env info
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 顯示環境路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">poetry env info --path
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 列出所有環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">poetry env list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># 切換 Python 版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">poetry env use python3.11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">poetry env use 3.12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="c1"># 刪除環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">poetry env remove python3.11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">poetry env remove --all&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="環境資訊範例輸出">環境資訊範例輸出&lt;/h5>





&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">Virtualenv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">Python: 3.11.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">Implementation: CPython
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">Path: /path/to/project/.venv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">Executable: /path/to/project/.venv/bin/python
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">Valid: True
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">Base
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">Platform: darwin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">OS: posix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">Python: 3.11.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">Path: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">Executable: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11/bin/python3.11&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="工作流四建構與發布">工作流四：建構與發布&lt;/h3>
&lt;h4 id="建構套件">建構套件&lt;/h4>





&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">&lt;span class="c1"># 建構 sdist 和 wheel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 僅建構 wheel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry build --format wheel
&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"># 建構結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">dist/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── my_package-1.0.0-py3-none-any.whl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── my_package-1.0.0.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="發布到-pypi">發布到 PyPI&lt;/h4>





&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">&lt;span class="c1"># 設定 PyPI token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry config pypi-token.pypi pypi-XXXXXXXXXXXX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 發布到 PyPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry publish
&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"># 建構並發布（一步完成）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">poetry publish --build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 發布到 TestPyPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">poetry config repositories.testpypi https://test.pypi.org/legacy/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">poetry config pypi-token.testpypi pypi-XXXXXXXXXXXX
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">poetry publish --repository testpypi&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="版本管理">版本管理&lt;/h5>





&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">&lt;span class="c1"># 查看當前版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">poetry version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 升級版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">poetry version patch &lt;span class="c1"># 1.0.0 -&amp;gt; 1.0.1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">poetry version minor &lt;span class="c1"># 1.0.0 -&amp;gt; 1.1.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">poetry version major &lt;span class="c1"># 1.0.0 -&amp;gt; 2.0.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 設定特定版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">poetry version 2.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 預發布版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">poetry version prepatch &lt;span class="c1"># 1.0.0 -&amp;gt; 1.0.1a0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">poetry version preminor &lt;span class="c1"># 1.0.0 -&amp;gt; 1.1.0a0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">poetry version premajor &lt;span class="c1"># 1.0.0 -&amp;gt; 2.0.0a0&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整-pyprojecttoml-範例">完整 pyproject.toml 範例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;poetry-core&amp;gt;=2.0&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;poetry.core.masonry.api&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&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 class="nx">project&lt;/span>&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my-awesome-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">description&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;A feature-rich Python library&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">readme&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">license&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;MIT&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">requires-python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;=3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">authors&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">13&lt;/span>&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Your Name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;you@example.com&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nx">keywords&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;library&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;utilities&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">classifiers&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">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Development Status :: 4 - Beta&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Intended Audience :: Developers&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;License :: OSI Approved :: MIT License&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Operating System :: OS Independent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.10&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.11&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.12&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.13&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Typing :: Typed&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">urls&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="nx">Homepage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="nx">Documentation&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://my-awesome-lib.readthedocs.io&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="nx">Repository&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib.git&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="nx">Changelog&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib/blob/main/CHANGELOG.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="nx">my-cli&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my_awesome_lib.cli:main&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">optional-dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="nx">yaml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pyyaml&amp;gt;=6.0&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;my-awesome-lib[yaml]&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="c"># ===== Poetry 特定設定 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="nx">packages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[{&lt;/span> &lt;span class="nx">include&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my_awesome_lib&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span> &lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="nx">python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="nx">requests&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^2.31&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">&lt;span class="nx">click&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^8.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">&lt;span class="nx">pytest&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^8.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">&lt;span class="nx">pytest-cov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^4.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">&lt;span class="nx">pytest-asyncio&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^0.23&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">&lt;span class="nx">mypy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^1.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="nx">ruff&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^0.4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="nx">optional&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="nx">mkdocs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^1.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl">&lt;span class="nx">mkdocs-material&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^9.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">&lt;span class="c"># ===== 工具設定 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ruff&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl">&lt;span class="nx">src&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">&lt;span class="nx">line-length&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">88&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl">&lt;span class="nx">target-version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;py310&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ruff&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl">&lt;span class="nx">select&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;E&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;W&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;F&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;I&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;B&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;C4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;UP&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mypy&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl">&lt;span class="nx">python_version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">78&lt;/span>&lt;span class="cl">&lt;span class="nx">strict&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">79&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">80&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pytest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ini_options&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">81&lt;/span>&lt;span class="cl">&lt;span class="nx">testpaths&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">82&lt;/span>&lt;span class="cl">&lt;span class="nx">addopts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;-v --tb=short&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="與其他工具的比較">與其他工具的比較&lt;/h2>
&lt;h3 id="poetry-vs-pip">Poetry vs pip&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>操作&lt;/th>
 &lt;th>pip&lt;/th>
 &lt;th>Poetry&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>安裝相依性&lt;/td>
 &lt;td>&lt;code>pip install -r requirements.txt&lt;/code>&lt;/td>
 &lt;td>&lt;code>poetry install&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新增相依性&lt;/td>
 &lt;td>手動編輯 requirements.txt&lt;/td>
 &lt;td>&lt;code>poetry add package&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>鎖定版本&lt;/td>
 &lt;td>&lt;code>pip freeze &amp;gt; requirements.txt&lt;/code>&lt;/td>
 &lt;td>自動更新 poetry.lock&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>建立環境&lt;/td>
 &lt;td>&lt;code>python -m venv .venv&lt;/code>&lt;/td>
 &lt;td>自動建立&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>執行命令&lt;/td>
 &lt;td>&lt;code>source .venv/bin/activate &amp;amp;&amp;amp; python&lt;/code>&lt;/td>
 &lt;td>&lt;code>poetry run python&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>建構套件&lt;/td>
 &lt;td>&lt;code>python -m build&lt;/code>&lt;/td>
 &lt;td>&lt;code>poetry build&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>發布套件&lt;/td>
 &lt;td>&lt;code>twine upload dist/*&lt;/code>&lt;/td>
 &lt;td>&lt;code>poetry publish&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="poetry-vs-setuptools">Poetry vs setuptools&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>setuptools&lt;/th>
 &lt;th>Poetry&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>設定格式&lt;/td>
 &lt;td>pyproject.toml + 可能需要 setup.py&lt;/td>
 &lt;td>僅 pyproject.toml&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>C 擴展支援&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;h3 id="poetry-vs-hatch">Poetry vs Hatch&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Hatch&lt;/th>
 &lt;th>Poetry&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>設計理念&lt;/td>
 &lt;td>PEP 標準優先&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>多環境（類似 tox）&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>hatchling&lt;/td>
 &lt;td>poetry-core&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;h2 id="實用技巧">實用技巧&lt;/h2>
&lt;h3 id="技巧一善用-lock-檔案">技巧一：善用 Lock 檔案&lt;/h3>





&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">&lt;span class="c1"># poetry.lock 的重要性&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># - 確保團隊成員、CI/CD 使用相同版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># - 應該提交到版本控制&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 根據 lock 檔案安裝（不更新）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">poetry install --no-update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證 lock 檔案與 pyproject.toml 一致&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">poetry check --lock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 匯出為 requirements.txt（部署用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">poetry &lt;span class="nb">export&lt;/span> -f requirements.txt -o requirements.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">poetry &lt;span class="nb">export&lt;/span> -f requirements.txt --with dev -o requirements-dev.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">poetry &lt;span class="nb">export&lt;/span> --without-hashes -o requirements.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="技巧二善用相依性群組">技巧二：善用相依性群組&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 開發相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="nx">pytest&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^8.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nx">ruff&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^0.4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c"># 可選群組（預設不安裝）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">optional&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="nx">mkdocs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^1.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c"># CI 專用群組&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ci&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nx">optional&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ci&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="nx">pytest-cov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^4.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="nx">codecov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^2.1&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">&lt;span class="c1"># 安裝特定群組&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">poetry install --with docs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">poetry install --with ci
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># CI 環境中的安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">poetry install --only main,ci&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="技巧三善用-extras">技巧三：善用 Extras&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">optional-dependencies&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="c"># 功能性 extras&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">yaml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pyyaml&amp;gt;=6.0&amp;#34;&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="nx">async&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;httpx&amp;gt;=0.24&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;aiofiles&amp;gt;=23.0&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c"># 完整安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;my-package[yaml,async]&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">&lt;span class="c1"># 使用者安裝方式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">pip install my-package &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">pip install &lt;span class="s2">&amp;#34;my-package[yaml]&amp;#34;&lt;/span> &lt;span class="c1"># 包含 YAML 支援&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">pip install &lt;span class="s2">&amp;#34;my-package[all]&amp;#34;&lt;/span> &lt;span class="c1"># 所有功能&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="技巧四本地相依性和-git-相依性">技巧四：本地相依性和 Git 相依性&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="c"># 本地路徑相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">my-local-lib&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;../my-local-lib&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">develop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&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>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c"># Git 相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="nx">my-git-lib&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">git&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/user/repo.git&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="nx">my-git-lib&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">git&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/user/repo.git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">branch&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;develop&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="nx">my-git-lib&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">git&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/user/repo.git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">tag&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;v1.0.0&amp;#34;&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="nx">my-git-lib&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">git&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/user/repo.git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">rev&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;abc123&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="技巧五平台特定相依性">技巧五：平台特定相依性&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="c"># 僅 Windows&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">pywin32&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^306&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">markers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;sys_platform == &amp;#39;win32&amp;#39;&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c"># 僅 Linux&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="nx">uvloop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^0.19&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">markers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;sys_platform == &amp;#39;linux&amp;#39;&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c"># Python 版本限制&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="nx">typing-extensions&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^4.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;3.11&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="技巧六poetry-腳本">技巧六：Poetry 腳本&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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="nx">my-cli&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my_package.cli:main&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">my-tool&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my_package.tools:run&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># src/my_package/cli.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">click&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nd">@click.command&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="nd">@click.option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;World&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Name to greet&amp;#34;&lt;/span>&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 class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Greet someone.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">click&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hello, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">!&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&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="n">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">&lt;span class="c1"># 安裝後即可使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">my-cli --name Python
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 輸出：Hello, Python!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="常見問題與解決">常見問題與解決&lt;/h2>
&lt;h3 id="問題一相依性解析衝突">問題一：相依性解析衝突&lt;/h3>





&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">&lt;span class="c1"># 錯誤訊息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">SolverProblemError: ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 解決方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 1. 檢視衝突詳情&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">poetry show --tree
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 2. 放寬版本限制&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">poetry add &lt;span class="s2">&amp;#34;package&amp;gt;=1.0,&amp;lt;3.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 3. 強制更新 lock 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">poetry lock --no-update&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="問題二虛擬環境問題">問題二：虛擬環境問題&lt;/h3>





&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">&lt;span class="c1"># 重建虛擬環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">poetry env remove --all
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">poetry install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 指定 Python 版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">poetry env use /usr/bin/python3.11&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="問題三cicd-快取">問題三：CI/CD 快取&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># GitHub Actions 範例&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Install Poetry&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">snok/install-poetry@v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">virtualenvs-create&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">virtualenvs-in-project&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Load cached venv&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/cache@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.venv&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">venv-${{ runner.os }}-${{ hashFiles(&amp;#39;**/poetry.lock&amp;#39;) }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Install dependencies&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">steps.cache.outputs.cache-hit != &amp;#39;true&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">poetry install --no-interaction&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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;strong>相依性鎖定&lt;/strong>&lt;/td>
 &lt;td>環境可重現、團隊一致&lt;/td>
 &lt;td>lock 檔案衝突、更新需謹慎&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>一體化工具&lt;/strong>&lt;/td>
 &lt;td>學習成本低、工作流統一&lt;/td>
 &lt;td>與其他工具整合需調整&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>虛擬環境整合&lt;/strong>&lt;/td>
 &lt;td>自動管理、不易混淆&lt;/td>
 &lt;td>自訂環境位置需設定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>建構與發布&lt;/strong>&lt;/td>
 &lt;td>流程簡化&lt;/td>
 &lt;td>不支援 C 擴展&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="練習">練習&lt;/h2>
&lt;h3 id="基礎練習建立-poetry-專案">基礎練習：建立 Poetry 專案&lt;/h3>
&lt;p>&lt;strong>目標&lt;/strong>：使用 Poetry 建立一個簡單的專案&lt;/p></description><content:encoded><![CDATA[<p>本案例展示如何使用 Poetry 管理現代 Python 專案的完整生命週期，從專案建立到套件發布。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/07-packaging/build-systems/" data-link-title="6.2 建構系統比較" data-link-desc="比較 setuptools、Poetry、Hatch 等建構系統">建構系統比較</a></li>
<li>Python 虛擬環境基礎</li>
<li>套件相依性概念</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現代專案的挑戰">現代專案的挑戰</h3>
<p>傳統的 Python 專案管理面臨以下問題：</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">傳統工作流程的痛點：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── 相依性管理
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│   ├── requirements.txt 無法鎖定間接相依性
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│   ├── pip freeze 產生的版本可能過於嚴格
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   └── 開發與生產環境相依性混在一起
</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></span><span class="line"><span class="ln"> 8</span><span class="cl">│   ├── 不同專案的環境容易混淆
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   └── 缺乏與專案的關聯性
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 建構與發布
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   ├── setup.py 設定複雜
</span></span><span class="line"><span class="ln">12</span><span class="cl">│   ├── 發布流程需要多個工具
</span></span><span class="line"><span class="ln">13</span><span class="cl">│   └── 版本管理不一致
</span></span><span class="line"><span class="ln">14</span><span class="cl">└── 團隊協作
</span></span><span class="line"><span class="ln">15</span><span class="cl">    ├── 環境難以重現
</span></span><span class="line"><span class="ln">16</span><span class="cl">    ├── 「在我電腦上可以跑」問題
</span></span><span class="line"><span class="ln">17</span><span class="cl">    └── 新成員上手困難</span></span></code></pre></div><h3 id="為什麼選擇-poetry">為什麼選擇 Poetry？</h3>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>pip + venv</th>
          <th>Poetry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>相依性鎖定</td>
          <td>手動（pip freeze）</td>
          <td>自動（poetry.lock）</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>需要 twine</td>
          <td>內建</td>
      </tr>
      <tr>
          <td>相依性群組</td>
          <td>無原生支援</td>
          <td>完整支援</td>
      </tr>
  </tbody>
</table>
<h2 id="解決方案">解決方案</h2>
<h3 id="安裝-poetry">安裝 Poetry</h3>





<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"><span class="c1"># 官方推薦的安裝方式（獨立安裝）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">curl -sSL https://install.python-poetry.org <span class="p">|</span> python3 -
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 或使用 pipx（推薦用於 CLI 工具）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pipx install poetry
</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"># 確認安裝</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">poetry --version</span></span></code></pre></div><h4 id="設定-poetry">設定 Poetry</h4>





<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"><span class="c1"># 在專案目錄中建立虛擬環境（推薦）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">poetry config virtualenvs.in-project <span class="nb">true</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 查看所有設定</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">poetry config --list</span></span></code></pre></div><h3 id="工作流一建立新專案">工作流一：建立新專案</h3>
<h4 id="使用-poetry-new完整專案結構">使用 poetry new（完整專案結構）</h4>





<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"><span class="c1"># 建立新專案</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry new my-awesome-lib
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 產生的目錄結構</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">my-awesome-lib/
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── README.md
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── my_awesome_lib/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   └── __init__.py
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">11</span><span class="cl">    └── __init__.py</span></span></code></pre></div>




<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"><span class="c1"># 使用 src layout（推薦用於函式庫）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry new --src my-awesome-lib
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 產生的目錄結構</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">my-awesome-lib/
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── README.md
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   └── my_awesome_lib/
</span></span><span class="line"><span class="ln">10</span><span class="cl">│       └── __init__.py
</span></span><span class="line"><span class="ln">11</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">12</span><span class="cl">    └── __init__.py</span></span></code></pre></div><h4 id="使用-poetry-init現有專案">使用 poetry init（現有專案）</h4>





<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"><span class="c1"># 在現有目錄初始化</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nb">cd</span> existing-project
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">poetry init
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 互動式問答</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># - Package name</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># - Version</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># - Description</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># - Author</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># - License</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># - Python version</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># - Dependencies</span></span></span></code></pre></div><h5 id="互動式初始化範例">互動式初始化範例</h5>





<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">This command will guide you through creating your pyproject.toml config.
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Package name [existing-project]:  my-package
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Version [0.1.0]:  1.0.0
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Description []:  A useful Python package
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Author [Your Name &lt;you@example.com&gt;, n to skip]:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">License []:  MIT
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Compatible Python versions [^3.10]:  ^3.10
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">Would you like to define your main dependencies interactively? (yes/no) [yes]
</span></span><span class="line"><span class="ln">11</span><span class="cl">Search for package to add (or leave blank to continue): requests
</span></span><span class="line"><span class="ln">12</span><span class="cl">...</span></span></code></pre></div><h3 id="工作流二管理相依性">工作流二：管理相依性</h3>
<h4 id="新增相依性">新增相依性</h4>





<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"><span class="c1"># 新增生產相依性</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry add requests
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">poetry add <span class="s2">&#34;httpx&gt;=0.24&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 新增開發相依性</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">poetry add pytest --group dev
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">poetry add ruff mypy --group dev
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 新增文件相依性</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">poetry add mkdocs mkdocs-material --group docs
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 新增可選相依性（extras）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">poetry add pyyaml --optional</span></span></code></pre></div><h5 id="pyprojecttoml-的變化">pyproject.toml 的變化</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">python</span> <span class="p">=</span> <span class="s2">&#34;^3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">requests</span> <span class="p">=</span> <span class="s2">&#34;^2.31.0&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">httpx</span> <span class="p">=</span> <span class="s2">&#34;&gt;=0.24&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">dev</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">pytest</span> <span class="p">=</span> <span class="s2">&#34;^8.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">ruff</span> <span class="p">=</span> <span class="s2">&#34;^0.4.0&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">mypy</span> <span class="p">=</span> <span class="s2">&#34;^1.10.0&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">docs</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">mkdocs</span> <span class="p">=</span> <span class="s2">&#34;^1.5.0&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">mkdocs-material</span> <span class="p">=</span> <span class="s2">&#34;^9.5.0&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">extras</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pyyaml&#34;</span><span class="p">]</span></span></span></code></pre></div><h4 id="移除相依性">移除相依性</h4>





<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"><span class="c1"># 移除生產相依性</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">poetry remove requests
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 移除開發相依性</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">poetry remove pytest --group dev</span></span></code></pre></div><h4 id="更新相依性">更新相依性</h4>





<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"><span class="c1"># 更新所有相依性</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry update
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 更新特定套件</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry update requests
</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"># 檢視可更新的套件</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">poetry show --outdated
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 檢視相依性樹</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">poetry show --tree</span></span></code></pre></div><h5 id="相依性樹範例輸出">相依性樹範例輸出</h5>





<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">requests 2.31.0 Python HTTP for Humans.
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── certifi &gt;=2017.4.17
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── charset-normalizer &gt;=2,&lt;4
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── idna &gt;=2.5,&lt;4
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── urllib3 &gt;=1.21.1,&lt;3</span></span></code></pre></div><h3 id="工作流三虛擬環境管理">工作流三：虛擬環境管理</h3>
<h4 id="自動環境管理">自動環境管理</h4>





<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"><span class="c1"># 安裝所有相依性（自動建立虛擬環境）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry install
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 僅安裝生產相依性</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry install --only main
</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"># 安裝包含特定群組</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">poetry install --with dev,docs
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 排除特定群組</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">poetry install --without docs
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 安裝 extras</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">poetry install --extras yaml
</span></span><span class="line"><span class="ln">15</span><span class="cl">poetry install --all-extras</span></span></code></pre></div><h4 id="環境操作">環境操作</h4>





<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"><span class="c1"># 進入虛擬環境 shell</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry shell
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 在虛擬環境中執行命令（不進入 shell）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry run python script.py
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">poetry run pytest
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">poetry run python -c <span class="s2">&#34;import my_package; print(my_package.__version__)&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 顯示環境資訊</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">poetry env info
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 顯示環境路徑</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">poetry env info --path
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 列出所有環境</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">poetry env list
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 切換 Python 版本</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">poetry env use python3.11
</span></span><span class="line"><span class="ln">20</span><span class="cl">poetry env use 3.12
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 刪除環境</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">poetry env remove python3.11
</span></span><span class="line"><span class="ln">24</span><span class="cl">poetry env remove --all</span></span></code></pre></div><h5 id="環境資訊範例輸出">環境資訊範例輸出</h5>





<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">Virtualenv
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">Python:         3.11.6
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">Implementation: CPython
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Path:           /path/to/project/.venv
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Executable:     /path/to/project/.venv/bin/python
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Valid:          True
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Base
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Platform:   darwin
</span></span><span class="line"><span class="ln">10</span><span class="cl">OS:         posix
</span></span><span class="line"><span class="ln">11</span><span class="cl">Python:     3.11.6
</span></span><span class="line"><span class="ln">12</span><span class="cl">Path:       /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11
</span></span><span class="line"><span class="ln">13</span><span class="cl">Executable: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11/bin/python3.11</span></span></code></pre></div><h3 id="工作流四建構與發布">工作流四：建構與發布</h3>
<h4 id="建構套件">建構套件</h4>





<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"><span class="c1"># 建構 sdist 和 wheel</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry build
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 僅建構 wheel</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry build --format wheel
</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"># 建構結果</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">dist/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── my_package-1.0.0-py3-none-any.whl
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── my_package-1.0.0.tar.gz</span></span></code></pre></div><h4 id="發布到-pypi">發布到 PyPI</h4>





<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"><span class="c1"># 設定 PyPI token</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry config pypi-token.pypi pypi-XXXXXXXXXXXX
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 發布到 PyPI</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry publish
</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"># 建構並發布（一步完成）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">poetry publish --build
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 發布到 TestPyPI</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">poetry config repositories.testpypi https://test.pypi.org/legacy/
</span></span><span class="line"><span class="ln">12</span><span class="cl">poetry config pypi-token.testpypi pypi-XXXXXXXXXXXX
</span></span><span class="line"><span class="ln">13</span><span class="cl">poetry publish --repository testpypi</span></span></code></pre></div><h5 id="版本管理">版本管理</h5>





<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"><span class="c1"># 查看當前版本</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">poetry version
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 升級版本</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">poetry version patch   <span class="c1"># 1.0.0 -&gt; 1.0.1</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">poetry version minor   <span class="c1"># 1.0.0 -&gt; 1.1.0</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">poetry version major   <span class="c1"># 1.0.0 -&gt; 2.0.0</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 設定特定版本</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">poetry version 2.0.0
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 預發布版本</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">poetry version prepatch  <span class="c1"># 1.0.0 -&gt; 1.0.1a0</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">poetry version preminor  <span class="c1"># 1.0.0 -&gt; 1.1.0a0</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">poetry version premajor  <span class="c1"># 1.0.0 -&gt; 2.0.0a0</span></span></span></code></pre></div><h3 id="完整-pyprojecttoml-範例">完整 pyproject.toml 範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;poetry-core&gt;=2.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;poetry.core.masonry.api&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-awesome-lib&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;A feature-rich Python library&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">readme</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">license</span> <span class="p">=</span> <span class="s2">&#34;MIT&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">authors</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">{</span> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Your Name&#34;</span><span class="p">,</span> <span class="nx">email</span> <span class="p">=</span> <span class="s2">&#34;you@example.com&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">keywords</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;library&#34;</span><span class="p">,</span> <span class="s2">&#34;utilities&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">classifiers</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;Development Status :: 4 - Beta&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;Intended Audience :: Developers&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;License :: OSI Approved :: MIT License&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;Operating System :: OS Independent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.11&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.12&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;Programming Language :: Python :: 3.13&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;Typing :: Typed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">Homepage</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="nx">Documentation</span> <span class="p">=</span> <span class="s2">&#34;https://my-awesome-lib.readthedocs.io&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="nx">Repository</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib.git&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="nx">Changelog</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib/blob/main/CHANGELOG.md&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="nx">my-cli</span> <span class="p">=</span> <span class="s2">&#34;my_awesome_lib.cli:main&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pyyaml&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;my-awesome-lib[yaml]&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c"># ===== Poetry 特定設定 =====</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[{</span> <span class="nx">include</span> <span class="p">=</span> <span class="s2">&#34;my_awesome_lib&#34;</span><span class="p">,</span> <span class="nx">from</span> <span class="p">=</span> <span class="s2">&#34;src&#34;</span> <span class="p">}]</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="nx">python</span> <span class="p">=</span> <span class="s2">&#34;^3.10&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="nx">requests</span> <span class="p">=</span> <span class="s2">&#34;^2.31&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="nx">click</span> <span class="p">=</span> <span class="s2">&#34;^8.1&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">dev</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="nx">pytest</span> <span class="p">=</span> <span class="s2">&#34;^8.0&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="nx">pytest-cov</span> <span class="p">=</span> <span class="s2">&#34;^4.1&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="nx">pytest-asyncio</span> <span class="p">=</span> <span class="s2">&#34;^0.23&#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="nx">mypy</span> <span class="p">=</span> <span class="s2">&#34;^1.10&#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nx">ruff</span> <span class="p">=</span> <span class="s2">&#34;^0.4&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">docs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="nx">optional</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">docs</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="nx">mkdocs</span> <span class="p">=</span> <span class="s2">&#34;^1.5&#34;</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="nx">mkdocs-material</span> <span class="p">=</span> <span class="s2">&#34;^9.5&#34;</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="c"># ===== 工具設定 =====</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="nx">src</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="nx">line-length</span> <span class="p">=</span> <span class="mi">88</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="nx">target-version</span> <span class="p">=</span> <span class="s2">&#34;py310&#34;</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">.</span><span class="nx">lint</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="nx">select</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="s2">&#34;W&#34;</span><span class="p">,</span> <span class="s2">&#34;F&#34;</span><span class="p">,</span> <span class="s2">&#34;I&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;C4&#34;</span><span class="p">,</span> <span class="s2">&#34;UP&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">
</span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">mypy</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="nx">python_version</span> <span class="p">=</span> <span class="s2">&#34;3.10&#34;</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="nx">strict</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">
</span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">pytest</span><span class="p">.</span><span class="nx">ini_options</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl"><span class="nx">testpaths</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;tests&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="nx">addopts</span> <span class="p">=</span> <span class="s2">&#34;-v --tb=short&#34;</span></span></span></code></pre></div><h2 id="與其他工具的比較">與其他工具的比較</h2>
<h3 id="poetry-vs-pip">Poetry vs pip</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>pip</th>
          <th>Poetry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>安裝相依性</td>
          <td><code>pip install -r requirements.txt</code></td>
          <td><code>poetry install</code></td>
      </tr>
      <tr>
          <td>新增相依性</td>
          <td>手動編輯 requirements.txt</td>
          <td><code>poetry add package</code></td>
      </tr>
      <tr>
          <td>鎖定版本</td>
          <td><code>pip freeze &gt; requirements.txt</code></td>
          <td>自動更新 poetry.lock</td>
      </tr>
      <tr>
          <td>建立環境</td>
          <td><code>python -m venv .venv</code></td>
          <td>自動建立</td>
      </tr>
      <tr>
          <td>執行命令</td>
          <td><code>source .venv/bin/activate &amp;&amp; python</code></td>
          <td><code>poetry run python</code></td>
      </tr>
      <tr>
          <td>建構套件</td>
          <td><code>python -m build</code></td>
          <td><code>poetry build</code></td>
      </tr>
      <tr>
          <td>發布套件</td>
          <td><code>twine upload dist/*</code></td>
          <td><code>poetry publish</code></td>
      </tr>
  </tbody>
</table>
<h3 id="poetry-vs-setuptools">Poetry vs setuptools</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>setuptools</th>
          <th>Poetry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>設定格式</td>
          <td>pyproject.toml + 可能需要 setup.py</td>
          <td>僅 pyproject.toml</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>C 擴展支援</td>
          <td>完整</td>
          <td>不支援</td>
      </tr>
      <tr>
          <td>生態系統</td>
          <td>最廣泛</td>
          <td>持續成長</td>
      </tr>
  </tbody>
</table>
<h3 id="poetry-vs-hatch">Poetry vs Hatch</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Hatch</th>
          <th>Poetry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>設計理念</td>
          <td>PEP 標準優先</td>
          <td>使用者體驗優先</td>
      </tr>
      <tr>
          <td>相依性鎖定</td>
          <td>無內建</td>
          <td>核心功能</td>
      </tr>
      <tr>
          <td>環境管理</td>
          <td>多環境（類似 tox）</td>
          <td>單一虛擬環境</td>
      </tr>
      <tr>
          <td>腳本系統</td>
          <td>完整</td>
          <td>基本</td>
      </tr>
      <tr>
          <td>建構後端</td>
          <td>hatchling</td>
          <td>poetry-core</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>開源函式庫</td>
          <td>應用程式、內部工具</td>
      </tr>
  </tbody>
</table>
<h2 id="實用技巧">實用技巧</h2>
<h3 id="技巧一善用-lock-檔案">技巧一：善用 Lock 檔案</h3>





<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"><span class="c1"># poetry.lock 的重要性</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># - 記錄所有相依性的精確版本（包含間接相依性）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># - 確保團隊成員、CI/CD 使用相同版本</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># - 應該提交到版本控制</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 根據 lock 檔案安裝（不更新）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">poetry install --no-update
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 驗證 lock 檔案與 pyproject.toml 一致</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">poetry check --lock
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 匯出為 requirements.txt（部署用）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">poetry <span class="nb">export</span> -f requirements.txt -o requirements.txt
</span></span><span class="line"><span class="ln">14</span><span class="cl">poetry <span class="nb">export</span> -f requirements.txt --with dev -o requirements-dev.txt
</span></span><span class="line"><span class="ln">15</span><span class="cl">poetry <span class="nb">export</span> --without-hashes -o requirements.txt</span></span></code></pre></div><h3 id="技巧二善用相依性群組">技巧二：善用相依性群組</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 開發相依性</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">dev</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">pytest</span> <span class="p">=</span> <span class="s2">&#34;^8.0&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">ruff</span> <span class="p">=</span> <span class="s2">&#34;^0.4&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c"># 可選群組（預設不安裝）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">docs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">optional</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">docs</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">mkdocs</span> <span class="p">=</span> <span class="s2">&#34;^1.5&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c"># CI 專用群組</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">ci</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">optional</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">ci</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nx">pytest-cov</span> <span class="p">=</span> <span class="s2">&#34;^4.1&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">codecov</span> <span class="p">=</span> <span class="s2">&#34;^2.1&#34;</span></span></span></code></pre></div>




<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"><span class="c1"># 安裝特定群組</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">poetry install --with docs
</span></span><span class="line"><span class="ln">3</span><span class="cl">poetry install --with ci
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># CI 環境中的安裝</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">poetry install --only main,ci</span></span></code></pre></div><h3 id="技巧三善用-extras">技巧三：善用 Extras</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c"># 功能性 extras</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pyyaml&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">async</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;httpx&gt;=0.24&#34;</span><span class="p">,</span> <span class="s2">&#34;aiofiles&gt;=23.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c"># 完整安裝</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;my-package[yaml,async]&#34;</span><span class="p">]</span></span></span></code></pre></div>




<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"><span class="c1"># 使用者安裝方式</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pip install my-package           <span class="c1"># 基本功能</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">pip install <span class="s2">&#34;my-package[yaml]&#34;</span>   <span class="c1"># 包含 YAML 支援</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">pip install <span class="s2">&#34;my-package[all]&#34;</span>    <span class="c1"># 所有功能</span></span></span></code></pre></div><h3 id="技巧四本地相依性和-git-相依性">技巧四：本地相依性和 Git 相依性</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c"># 本地路徑相依性</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">my-local-lib</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;../my-local-lib&#34;</span><span class="p">,</span> <span class="nx">develop</span> <span class="p">=</span> <span class="kc">true</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c"># Git 相依性</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">my-git-lib</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">git</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/user/repo.git&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nx">my-git-lib</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">git</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/user/repo.git&#34;</span><span class="p">,</span> <span class="nx">branch</span> <span class="p">=</span> <span class="s2">&#34;develop&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="nx">my-git-lib</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">git</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/user/repo.git&#34;</span><span class="p">,</span> <span class="nx">tag</span> <span class="p">=</span> <span class="s2">&#34;v1.0.0&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nx">my-git-lib</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">git</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/user/repo.git&#34;</span><span class="p">,</span> <span class="nx">rev</span> <span class="p">=</span> <span class="s2">&#34;abc123&#34;</span> <span class="p">}</span></span></span></code></pre></div><h3 id="技巧五平台特定相依性">技巧五：平台特定相依性</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c"># 僅 Windows</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">pywin32</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;^306&#34;</span><span class="p">,</span> <span class="nx">markers</span> <span class="p">=</span> <span class="s2">&#34;sys_platform == &#39;win32&#39;&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c"># 僅 Linux</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">uvloop</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;^0.19&#34;</span><span class="p">,</span> <span class="nx">markers</span> <span class="p">=</span> <span class="s2">&#34;sys_platform == &#39;linux&#39;&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c"># Python 版本限制</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nx">typing-extensions</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;^4.0&#34;</span><span class="p">,</span> <span class="nx">python</span> <span class="p">=</span> <span class="s2">&#34;&lt;3.11&#34;</span> <span class="p">}</span></span></span></code></pre></div><h3 id="技巧六poetry-腳本">技巧六：Poetry 腳本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">my-cli</span> <span class="p">=</span> <span class="s2">&#34;my_package.cli:main&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">my-tool</span> <span class="p">=</span> <span class="s2">&#34;my_package.tools:run&#34;</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># src/my_package/cli.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">click</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nd">@click.command</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@click.option</span><span class="p">(</span><span class="s2">&#34;--name&#34;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&#34;World&#34;</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s2">&#34;Name to greet&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Greet someone.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">click</span><span class="o">.</span><span class="n">echo</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div>




<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"><span class="c1"># 安裝後即可使用</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">my-cli --name Python
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 輸出：Hello, Python!</span></span></span></code></pre></div><h2 id="常見問題與解決">常見問題與解決</h2>
<h3 id="問題一相依性解析衝突">問題一：相依性解析衝突</h3>





<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"><span class="c1"># 錯誤訊息</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">SolverProblemError: ...
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 解決方法</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 1. 檢視衝突詳情</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">poetry show --tree
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 2. 放寬版本限制</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">poetry add <span class="s2">&#34;package&gt;=1.0,&lt;3.0&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 3. 強制更新 lock 檔案</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">poetry lock --no-update</span></span></code></pre></div><h3 id="問題二虛擬環境問題">問題二：虛擬環境問題</h3>





<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"><span class="c1"># 重建虛擬環境</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">poetry env remove --all
</span></span><span class="line"><span class="ln">3</span><span class="cl">poetry install
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 指定 Python 版本</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">poetry env use /usr/bin/python3.11</span></span></code></pre></div><h3 id="問題三cicd-快取">問題三：CI/CD 快取</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># GitHub Actions 範例</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Poetry</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">snok/install-poetry@v1</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">virtualenvs-create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">virtualenvs-in-project</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Load cached venv</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">.venv</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">    </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">venv-${{ runner.os }}-${{ hashFiles(&#39;**/poetry.lock&#39;) }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install dependencies</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">  </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.cache.outputs.cache-hit != &#39;true&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">  </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">poetry install --no-interaction</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>優點</th>
          <th>缺點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>相依性鎖定</strong></td>
          <td>環境可重現、團隊一致</td>
          <td>lock 檔案衝突、更新需謹慎</td>
      </tr>
      <tr>
          <td><strong>一體化工具</strong></td>
          <td>學習成本低、工作流統一</td>
          <td>與其他工具整合需調整</td>
      </tr>
      <tr>
          <td><strong>虛擬環境整合</strong></td>
          <td>自動管理、不易混淆</td>
          <td>自訂環境位置需設定</td>
      </tr>
      <tr>
          <td><strong>建構與發布</strong></td>
          <td>流程簡化</td>
          <td>不支援 C 擴展</td>
      </tr>
  </tbody>
</table>
<h2 id="練習">練習</h2>
<h3 id="基礎練習建立-poetry-專案">基礎練習：建立 Poetry 專案</h3>
<p><strong>目標</strong>：使用 Poetry 建立一個簡單的專案</p>
<ol>
<li>使用 <code>poetry new --src my-utils</code> 建立專案</li>
<li>新增 <code>requests</code> 作為生產相依性</li>
<li>新增 <code>pytest</code> 和 <code>ruff</code> 作為開發相依性</li>
<li>執行 <code>poetry install</code> 並驗證環境</li>
</ol>
<h3 id="進階練習設定相依性群組">進階練習：設定相依性群組</h3>
<p><strong>目標</strong>：建立完整的相依性管理結構</p>
<ol>
<li>建立 <code>dev</code>、<code>docs</code>、<code>ci</code> 三個群組</li>
<li>將 <code>docs</code> 和 <code>ci</code> 設為可選群組</li>
<li>練習使用 <code>--with</code> 和 <code>--without</code> 選項</li>
</ol>
<h3 id="挑戰題完整發布流程">挑戰題：完整發布流程</h3>
<p><strong>目標</strong>：將專案發布到 TestPyPI</p>
<ol>
<li>設定 TestPyPI repository</li>
<li>使用 <code>poetry version</code> 管理版本</li>
<li>執行 <code>poetry build</code> 建構套件</li>
<li>執行 <code>poetry publish --repository testpypi</code> 發布</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://python-poetry.org/docs/">Poetry 官方文件</a></li>
<li><a href="https://python-poetry.org/docs/cli/">Poetry CLI 參考</a></li>
<li><a href="https://peps.python.org/pep-0621/">pyproject.toml 規範 (PEP 621)</a></li>
<li><a href="https://packaging.python.org/">Python 打包使用者指南</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/07-packaging/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的打包發布案例">案例研究</a></p>
]]></content:encoded></item><item><title>案例：記憶體優化</title><link>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/config_loader.py&lt;/code> 的實際程式碼，展示如何用 &lt;code>__slots__&lt;/code> 和 &lt;code>weakref&lt;/code> 優化記憶體使用。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/memory-gc/" data-link-title="3.2 記憶體管理與垃圾回收" data-link-desc="理解 Python 的記憶體管理機制">4.2 記憶體管理與垃圾回收&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>config_loader.py&lt;/code> 使用全域字典作為快取，這是一個常見的設計模式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># Global cache variables&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">_agents_config_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&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">_quality_rules_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入代理人配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用模組層級變數作為快取，避免重複讀取檔案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_config_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除配置快取（用於測試或配置熱更新）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這種設計簡單直觀，但當系統需要快取更複雜的物件時，會遇到記憶體問題。&lt;/p>
&lt;h3 id="記憶體問題">記憶體問題&lt;/h3>
&lt;p>當快取大量物件時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Python 字典有額外開銷&lt;/strong>：每個字典需要維護 hash table、keys、values&lt;/li>
&lt;li>&lt;strong>物件的 &lt;code>__dict__&lt;/code> 佔用記憶體&lt;/strong>：每個實例都有自己的屬性字典&lt;/li>
&lt;li>&lt;strong>快取可能導致記憶體洩漏&lt;/strong>：強引用阻止物件被回收&lt;/li>
&lt;/ul>
&lt;p>讓我們用一個更複雜的快取場景來說明問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItem&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="s2">&amp;#34;&amp;#34;&amp;#34;配置項目，模擬複雜的快取物件&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&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 class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># Create a config item and measure memory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;database.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># Object size&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ConfigItem size: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># ConfigItem size: 48 bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># But the real cost is in __dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;__dict__ size: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="c1"># __dict__ size: 184 bytes&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>當快取數萬個這樣的物件時，記憶體開銷會非常可觀。&lt;/p>
&lt;hr>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="優化目標">優化目標&lt;/h3>
&lt;ol>
&lt;li>減少每個物件的記憶體佔用&lt;/li>
&lt;li>避免快取導致的記憶體洩漏&lt;/li>
&lt;li>保持 API 不變&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1使用-__slots__-減少物件大小">步驟 1：使用 &lt;code>__slots__&lt;/code> 減少物件大小&lt;/h4>
&lt;p>&lt;code>__slots__&lt;/code> 告訴 Python 這個類別只會有哪些屬性，讓直譯器可以用更緊湊的方式儲存資料：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItemWithoutSlots&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="s2">&amp;#34;&amp;#34;&amp;#34;標準類別，使用 __dict__&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&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 class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItemWithSlots&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;使用 __slots__ 優化記憶體&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="vm">__slots__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;key&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;metadata&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;access_count&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;last_accessed&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="c1"># Compare memory usage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="n">item_without&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItemWithoutSlots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;db.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;str&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="n">item_with&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItemWithSlots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;db.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;str&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Without __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_without&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;With __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_with&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="c1"># The real difference is __dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;__dict__ overhead: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_without&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="c1"># item_with has no __dict__!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">item_with&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">AttributeError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;No __dict__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="記憶體結構比較">記憶體結構比較&lt;/h5>





&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">沒有 __slots__:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">┌──────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ PyObject header (16 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ __dict__ 指標 (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ __weakref__ 指標 (8 B) │
&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">│ __dict__ (separate object): │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ - hash table (64 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ - keys array (40 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ - values array (40 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ - key strings (~80 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ Total: ~256 bytes per object │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">└──────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">有 __slots__:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">┌──────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">│ PyObject header (16 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">│ key slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">│ value slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">│ metadata slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">│ access_count slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">│ last_accessed slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">│ Total: ~56 bytes per object │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">└──────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="大量物件的記憶體節省">大量物件的記憶體節省&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tracemalloc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10000&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="s2">&amp;#34;&amp;#34;&amp;#34;Measure memory for creating multiple objects&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">objects&lt;/span> &lt;span class="o">=&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="bp">cls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;key_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;value_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">i&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="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">count&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">current&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_traced_memory&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">current&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objects&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># Measure both classes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="n">mem_without&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak_without&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ConfigItemWithoutSlots&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="n">mem_with&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak_with&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ConfigItemWithSlots&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Without __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;With __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_with&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Savings: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">mem_with&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Ratio: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">mem_with&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.1f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">x&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="c1"># Typical output:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="c1"># Without __slots__: 3.82 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="c1"># With __slots__: 1.15 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="c1"># Savings: 2.67 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="c1"># Ratio: 3.3x&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2使用-weakref-避免強引用">步驟 2：使用 weakref 避免強引用&lt;/h4>
&lt;p>&lt;code>weakref&lt;/code> 讓我們可以引用物件，但不阻止它被垃圾回收：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/config_loader.py</code> 的實際程式碼，展示如何用 <code>__slots__</code> 和 <code>weakref</code> 優化記憶體使用。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制</a></li>
<li><a href="/blog/python-advanced/04-cpython-internals/memory-gc/" data-link-title="3.2 記憶體管理與垃圾回收" data-link-desc="理解 Python 的記憶體管理機制">4.2 記憶體管理與垃圾回收</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>config_loader.py</code> 使用全域字典作為快取，這是一個常見的設計模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Global cache variables</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_quality_rules_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    使用模組層級變數作為快取，避免重複讀取檔案。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;清除配置快取（用於測試或配置熱更新）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span><span class="p">,</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><p>這種設計簡單直觀，但當系統需要快取更複雜的物件時，會遇到記憶體問題。</p>
<h3 id="記憶體問題">記憶體問題</h3>
<p>當快取大量物件時：</p>
<ul>
<li><strong>Python 字典有額外開銷</strong>：每個字典需要維護 hash table、keys、values</li>
<li><strong>物件的 <code>__dict__</code> 佔用記憶體</strong>：每個實例都有自己的屬性字典</li>
<li><strong>快取可能導致記憶體洩漏</strong>：強引用阻止物件被回收</li>
</ul>
<p>讓我們用一個更複雜的快取場景來說明問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItem</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置項目，模擬複雜的快取物件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Create a config item and measure memory</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">item</span> <span class="o">=</span> <span class="n">ConfigItem</span><span class="p">(</span><span class="s2">&#34;database.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Object size</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ConfigItem size: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># ConfigItem size: 48 bytes</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># But the real cost is in __dict__</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;__dict__ size: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># __dict__ size: 184 bytes</span></span></span></code></pre></div><p>當快取數萬個這樣的物件時，記憶體開銷會非常可觀。</p>
<hr>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="優化目標">優化目標</h3>
<ol>
<li>減少每個物件的記憶體佔用</li>
<li>避免快取導致的記憶體洩漏</li>
<li>保持 API 不變</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1使用-__slots__-減少物件大小">步驟 1：使用 <code>__slots__</code> 減少物件大小</h4>
<p><code>__slots__</code> 告訴 Python 這個類別只會有哪些屬性，讓直譯器可以用更緊湊的方式儲存資料：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItemWithoutSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;標準類別，使用 __dict__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItemWithSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 __slots__ 優化記憶體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;metadata&#39;</span><span class="p">,</span> <span class="s1">&#39;access_count&#39;</span><span class="p">,</span> <span class="s1">&#39;last_accessed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># Compare memory usage</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">item_without</span> <span class="o">=</span> <span class="n">ConfigItemWithoutSlots</span><span class="p">(</span><span class="s2">&#34;db.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;str&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">item_with</span> <span class="o">=</span> <span class="n">ConfigItemWithSlots</span><span class="p">(</span><span class="s2">&#34;db.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;str&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Without __slots__: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_without</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;With __slots__:    </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_with</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># The real difference is __dict__</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;__dict__ overhead: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_without</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># item_with has no __dict__!</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">item_with</span><span class="o">.</span><span class="vm">__dict__</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">except</span> <span class="ne">AttributeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No __dict__: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h5 id="記憶體結構比較">記憶體結構比較</h5>





<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">沒有 __slots__:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">┌──────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│ PyObject header         (16 B)   │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│ __dict__ 指標            (8 B)   │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│ __weakref__ 指標         (8 B)   │
</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">│ __dict__ (separate object):      │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   - hash table          (64 B)   │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   - keys array          (40 B)   │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│   - values array        (40 B)   │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   - key strings        (~80 B)   │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│                                  │
</span></span><span class="line"><span class="ln">13</span><span class="cl">│ Total: ~256 bytes per object     │
</span></span><span class="line"><span class="ln">14</span><span class="cl">└──────────────────────────────────┘
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">有 __slots__:
</span></span><span class="line"><span class="ln">17</span><span class="cl">┌──────────────────────────────────┐
</span></span><span class="line"><span class="ln">18</span><span class="cl">│ PyObject header         (16 B)   │
</span></span><span class="line"><span class="ln">19</span><span class="cl">│ key slot                 (8 B)   │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│ value slot               (8 B)   │
</span></span><span class="line"><span class="ln">21</span><span class="cl">│ metadata slot            (8 B)   │
</span></span><span class="line"><span class="ln">22</span><span class="cl">│ access_count slot        (8 B)   │
</span></span><span class="line"><span class="ln">23</span><span class="cl">│ last_accessed slot       (8 B)   │
</span></span><span class="line"><span class="ln">24</span><span class="cl">│                                  │
</span></span><span class="line"><span class="ln">25</span><span class="cl">│ Total: ~56 bytes per object      │
</span></span><span class="line"><span class="ln">26</span><span class="cl">└──────────────────────────────────┘</span></span></code></pre></div><h5 id="大量物件的記憶體節省">大量物件的記憶體節省</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">measure_memory</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure memory for creating multiple objects&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">objects</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">cls</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;key_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</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></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">current</span><span class="p">,</span> <span class="n">peak</span><span class="p">,</span> <span class="n">objects</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Measure both classes</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">mem_without</span><span class="p">,</span> <span class="n">peak_without</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">measure_memory</span><span class="p">(</span><span class="n">ConfigItemWithoutSlots</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">mem_with</span><span class="p">,</span> <span class="n">peak_with</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">measure_memory</span><span class="p">(</span><span class="n">ConfigItemWithSlots</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Without __slots__: </span><span class="si">{</span><span class="n">mem_without</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;With __slots__:    </span><span class="si">{</span><span class="n">mem_with</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Savings:           </span><span class="si">{</span><span class="p">(</span><span class="n">mem_without</span> <span class="o">-</span> <span class="n">mem_with</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Ratio:             </span><span class="si">{</span><span class="n">mem_without</span> <span class="o">/</span> <span class="n">mem_with</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># Typical output:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Without __slots__: 3.82 MB</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># With __slots__:    1.15 MB</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># Savings:           2.67 MB</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># Ratio:             3.3x</span></span></span></code></pre></div><h4 id="步驟-2使用-weakref-避免強引用">步驟 2：使用 weakref 避免強引用</h4>
<p><code>weakref</code> 讓我們可以引用物件，但不阻止它被垃圾回收：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">CacheableConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;可以被弱引用的配置物件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;_data&#39;</span><span class="p">,</span> <span class="s1">&#39;__weakref__&#39;</span><span class="p">]</span>  <span class="c1"># Note: __weakref__ slot</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;CacheableConfig(</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">key</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="si">!r}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Create object and weak reference</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">weak_ref</span> <span class="o">=</span> <span class="n">weakref</span><span class="o">.</span><span class="n">ref</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Object exists: </span><span class="si">{</span><span class="n">weak_ref</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># Object exists: CacheableConfig(&#39;app.name&#39;, &#39;MyApp&#39;)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># Delete the strong reference</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">del</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;After del: </span><span class="si">{</span><span class="n">weak_ref</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># After del: None</span></span></span></code></pre></div><h5 id="使用-callback-追蹤物件回收">使用 callback 追蹤物件回收</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">on_finalize</span><span class="p">(</span><span class="n">ref</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Callback when object is garbage collected&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Object was garbage collected!&#34;</span><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="n">config</span> <span class="o">=</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;db.port&#34;</span><span class="p">,</span> <span class="s2">&#34;5432&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">weak_ref</span> <span class="o">=</span> <span class="n">weakref</span><span class="o">.</span><span class="n">ref</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="n">on_finalize</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deleting object...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">del</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Output: Object was garbage collected!</span></span></span></code></pre></div><h4 id="步驟-3使用-weakvaluedictionary">步驟 3：使用 WeakValueDictionary</h4>
<p><code>WeakValueDictionary</code> 是實作自動清理快取的利器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">WeakCache</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Auto-cleaning cache using weak references.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Objects are automatically removed from cache when no strong
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    references exist outside the cache.
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">T</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">factory</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        Get item from cache, creating it if necessary.
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            key: Cache key
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">            factory: Function to create value if not cached
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">            Cached or newly created value
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="n">factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">def</span> <span class="fm">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Return cache statistics&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">/</span> <span class="n">total</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1%</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="c1"># Demo: automatic cleanup</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="n">cache</span> <span class="o">=</span> <span class="n">WeakCache</span><span class="p">[</span><span class="n">CacheableConfig</span><span class="p">]()</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"># Create and cache object</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="n">config1</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="n">config2</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">))</span>  <span class="c1"># Cache hit</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cache size: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># 1</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Same object: </span><span class="si">{</span><span class="n">config1</span> <span class="ow">is</span> <span class="n">config2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Stats: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># hits=1, misses=1</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"># Delete strong reference</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="k">del</span> <span class="n">config1</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="k">del</span> <span class="n">config2</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="c1"># Object is garbage collected, cache is auto-cleaned</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">
</span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cache size after cleanup: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># 0</span></span></span></code></pre></div><h4 id="步驟-4測量記憶體使用">步驟 4：測量記憶體使用</h4>
<p>使用 <code>sys.getsizeof</code> 和 <code>tracemalloc</code> 進行精確測量：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pympler</span> <span class="kn">import</span> <span class="n">asizeof</span>  <span class="c1"># pip install pympler</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">measure_object_size</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Object&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure object size using different methods&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Basic size (doesn&#39;t include referenced objects)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">basic</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># Deep size (includes all referenced objects)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># Using pympler for accurate measurement</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">deep</span> <span class="o">=</span> <span class="n">asizeof</span><span class="o">.</span><span class="n">asizeof</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">label</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  sys.getsizeof: </span><span class="si">{</span><span class="n">basic</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  pympler deep:  </span><span class="si">{</span><span class="n">deep</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">basic</span><span class="p">,</span> <span class="n">deep</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># Compare different object types</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">item_without</span> <span class="o">=</span> <span class="n">ConfigItemWithoutSlots</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">,</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">item_with</span> <span class="o">=</span> <span class="n">ConfigItemWithSlots</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">,</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">measure_object_size</span><span class="p">(</span><span class="n">item_without</span><span class="p">,</span> <span class="s2">&#34;Without __slots__&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="n">measure_object_size</span><span class="p">(</span><span class="n">item_with</span><span class="p">,</span> <span class="s2">&#34;With __slots__&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Using tracemalloc for allocation tracking</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">track_allocations</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Track memory allocations during execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># Simulate creating many cached objects</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">items</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">items</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ConfigItemWithSlots</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;active&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="c1"># Get snapshot</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">snapshot</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">top_stats</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="n">statistics</span><span class="p">(</span><span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Top 5 memory allocations:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">top_stats</span><span class="p">[:</span><span class="mi">5</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">stat</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># Get traced memory</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Current memory: </span><span class="si">{</span><span class="n">current</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Peak memory:    </span><span class="si">{</span><span class="n">peak</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">return</span> <span class="n">items</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="n">track_allocations</span><span class="p">()</span></span></span></code></pre></div><h5 id="比較記憶體差異的完整腳本">比較記憶體差異的完整腳本</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">StandardConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Standard class with __dict__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">metadata</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">SlottedConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Optimized with __slots__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;metadata&#39;</span><span class="p">,</span> <span class="s1">&#39;hits&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">metadata</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">class</span> <span class="nc">DataclassConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using dataclass&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">key</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">hits</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">slots</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>  <span class="c1"># Python 3.10+</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">class</span> <span class="nc">SlottedDataclass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Dataclass with __slots__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">key</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">hits</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_memory</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10000</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Benchmark memory usage for a class&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">objects</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="bp">cls</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;key_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="n">per_object</span> <span class="o">=</span> <span class="n">current</span> <span class="o">/</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">label</span> <span class="ow">or</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">:</span><span class="s2">25</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">          <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">current</span><span class="o">/</span><span class="mi">1024</span><span class="si">:</span><span class="s2">8.1f</span><span class="si">}</span><span class="s2"> KB | &#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">          <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">per_object</span><span class="si">:</span><span class="s2">6.1f</span><span class="si">}</span><span class="s2"> B/obj&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="k">return</span> <span class="n">objects</span>  <span class="c1"># Keep reference to prevent GC</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Class&#39;</span><span class="si">:</span><span class="s2">25</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="s1">&#39;Total&#39;</span><span class="si">:</span><span class="s2">&gt;10</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="s1">&#39;Per Object&#39;</span><span class="si">:</span><span class="s2">&gt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">55</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">StandardConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">SlottedConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">DataclassConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">SlottedDataclass</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"># Typical output:</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="c1"># Class                     |      Total |  Per Object</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="c1"># -------------------------------------------------------</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="c1"># StandardConfig            |   2578.5 KB |  263.6 B/obj</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># SlottedConfig             |    859.4 KB |   87.9 B/obj</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="c1"># DataclassConfig           |   2656.3 KB |  271.6 B/obj</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="c1"># SlottedDataclass          |    898.4 KB |   91.9 B/obj</span></span></span></code></pre></div><hr>
<h3 id="完整程式碼">完整程式碼</h3>
<p>以下是整合所有優化技術的完整實作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Memory-optimized configuration cache system.
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">This module demonstrates:
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">- Using __slots__ to reduce object memory footprint
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">- Using weakref for automatic cache cleanup
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">- Using tracemalloc for memory profiling
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">Based on patterns from .claude/lib/config_loader.py
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigEntry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    Memory-optimized configuration entry.
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">    Uses __slots__ to reduce memory footprint by ~3x compared
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">    to regular classes.
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;source&#39;</span><span class="p">,</span> <span class="s1">&#39;loaded_at&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="s1">&#39;access_count&#39;</span><span class="p">,</span> <span class="s1">&#39;__weakref__&#39;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="n">source</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">loaded_at</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;ConfigEntry(key=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">key</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;value=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;accesses=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">access_count</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">def</span> <span class="nf">touch</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Record an access to this entry&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="k">class</span> <span class="nc">SmartConfigCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">    Smart configuration cache with automatic memory management.
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">    Features:
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">    - Weak references for automatic cleanup
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    - Memory usage tracking
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">    - Hit/miss statistics
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">    - Optional size limits
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s2">        cache = SmartConfigCache(max_size=1000)
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">        # Get or create config
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s2">        config = cache.get_or_create(
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s2">            &#34;database.host&#34;,
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="s2">            lambda: ConfigEntry(&#34;database.host&#34;, &#34;localhost&#34;, &#34;env&#34;)
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="s2">        )
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">        # Check stats
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">        print(cache.stats())
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">        Initialize the cache.
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="s2">            max_size: Maximum number of entries. None for unlimited.
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ConfigEntry</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ConfigEntry</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>  <span class="c1"># Keep important items</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span> <span class="o">=</span> <span class="n">max_size</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ConfigEntry</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        Get entry from cache.
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">            ConfigEntry if found, None otherwise
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="c1"># Check strong refs first</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">entry</span><span class="o">.</span><span class="n">touch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="c1"># Then check weak refs</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">entry</span><span class="o">.</span><span class="n">touch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">def</span> <span class="nf">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">factory</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">ConfigEntry</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="n">keep_strong</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ConfigEntry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        Get existing entry or create new one.
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">            factory: Function to create entry if not found
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">            keep_strong: If True, keep a strong reference (won&#39;t auto-cleanup)
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="s2">            Existing or newly created ConfigEntry
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="c1"># Create new entry</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="n">factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="k">if</span> <span class="n">keep_strong</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_enforce_size_limit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">def</span> <span class="nf">_enforce_size_limit</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Evict old entries if cache is full&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="k">while</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">            <span class="c1"># Evict least accessed entry</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="n">min_key</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">keys</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">                <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">k</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">access_count</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">min_key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="k">def</span> <span class="nf">pin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">        Pin an entry to prevent automatic cleanup.
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="s2">            True if entry was pinned, False if not found
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_enforce_size_limit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="k">def</span> <span class="nf">unpin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="s2">        Unpin an entry to allow automatic cleanup.
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">196</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="s2">            True if entry was unpinned, False if not found
</span></span></span><span class="line"><span class="ln">198</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="k">def</span> <span class="nf">clear</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Clear all cached entries&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="s2">        Get cache statistics.
</span></span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">            Dict with hits, misses, hit_rate, size, pinned, evictions
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">/</span> <span class="n">total</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1%</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="s2">&#34;total_size&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">            <span class="s2">&#34;weak_refs&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">            <span class="s2">&#34;pinned&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="s2">&#34;evictions&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">
</span></span><span class="line"><span class="ln">229</span><span class="cl">    <span class="k">def</span> <span class="nf">memory_usage</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s2">        Estimate memory usage of cached entries.
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="s2">            Dict with entry_count, estimated_bytes, per_entry_bytes
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">        <span class="n">all_entries</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">values</span><span class="p">())</span> <span class="o">+</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">all_entries</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">                <span class="s2">&#34;entry_count&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">                <span class="s2">&#34;estimated_bytes&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">                <span class="s2">&#34;per_entry_bytes&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">        <span class="c1"># Estimate based on first entry</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">        <span class="n">sample</span> <span class="o">=</span> <span class="n">all_entries</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">        <span class="n">per_entry</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">sample</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">            <span class="s2">&#34;entry_count&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_entries</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">            <span class="s2">&#34;estimated_bytes&#34;</span><span class="p">:</span> <span class="n">per_entry</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_entries</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">            <span class="s2">&#34;per_entry_bytes&#34;</span><span class="p">:</span> <span class="n">per_entry</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">
</span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="k">def</span> <span class="nf">demo_memory_optimization</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Demonstrate memory optimization techniques&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">
</span></span><span class="line"><span class="ln">258</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Memory Optimization Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="c1"># Start memory tracking</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="n">snapshot1</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="c1"># Create cache and populate</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="n">cache</span> <span class="o">=</span> <span class="n">SmartConfigCache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="c1"># Simulate loading many configurations</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="n">entries</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">            <span class="k">lambda</span> <span class="n">i</span><span class="o">=</span><span class="n">i</span><span class="p">:</span> <span class="n">ConfigEntry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">                <span class="s2">&#34;demo&#34;</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">keep_strong</span><span class="o">=</span><span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">)</span>  <span class="c1"># Pin first 100</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="n">entries</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">    <span class="c1"># Take snapshot after creation</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="n">snapshot2</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl">    <span class="c1"># Print stats</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Cache Statistics:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Memory Usage:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">    <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">cache</span><span class="o">.</span><span class="n">memory_usage</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">value</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="c1"># Show memory diff</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">diff</span> <span class="o">=</span> <span class="n">snapshot2</span><span class="o">.</span><span class="n">compare_to</span><span class="p">(</span><span class="n">snapshot1</span><span class="p">,</span> <span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Top 5 Memory Allocations:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">diff</span><span class="p">[:</span><span class="mi">5</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">stat</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="c1"># Demo weak reference cleanup</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Weak Reference Cleanup Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Before cleanup - Cache size: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()[</span><span class="s1">&#39;total_size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="c1"># Delete external references to unpinned entries</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="k">del</span> <span class="n">entries</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;After cleanup  - Cache size: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()[</span><span class="s1">&#39;total_size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;(Only pinned entries remain)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">demo_memory_optimization</span><span class="p">()</span></span></span></code></pre></div><hr>
<h3 id="使用範例">使用範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">memory_optimized_cache</span> <span class="kn">import</span> <span class="n">SmartConfigCache</span><span class="p">,</span> <span class="n">ConfigEntry</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Initialize cache with size limit</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">cache</span> <span class="o">=</span> <span class="n">SmartConfigCache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># Load configuration</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">load_database_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Factory function to load database config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">ConfigEntry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">key</span><span class="o">=</span><span class="s2">&#34;database&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">value</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="s2">&#34;host&#34;</span><span class="p">:</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s2">&#34;port&#34;</span><span class="p">:</span> <span class="mi">5432</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;myapp&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">source</span><span class="o">=</span><span class="s2">&#34;config.yaml&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># Get or create (with strong reference for important config)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">db_config</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;database&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">load_database_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">keep_strong</span><span class="o">=</span><span class="kc">True</span>  <span class="c1"># Keep in memory</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Database host: </span><span class="si">{</span><span class="n">db_config</span><span class="o">.</span><span class="n">value</span><span class="p">[</span><span class="s1">&#39;host&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Temporary config (will be auto-cleaned when not referenced)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">temp_config</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;temp.setting&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">lambda</span><span class="p">:</span> <span class="n">ConfigEntry</span><span class="p">(</span><span class="s2">&#34;temp.setting&#34;</span><span class="p">,</span> <span class="s2">&#34;temporary&#34;</span><span class="p">,</span> <span class="s2">&#34;runtime&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># Check statistics</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"># {&#39;hits&#39;: 0, &#39;misses&#39;: 2, &#39;hit_rate&#39;: &#39;0.0%&#39;,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1">#  &#39;total_size&#39;: 2, &#39;weak_refs&#39;: 1, &#39;pinned&#39;: 1, &#39;evictions&#39;: 0}</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># Memory usage</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">cache</span><span class="o">.</span><span class="n">memory_usage</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"># {&#39;entry_count&#39;: 2, &#39;estimated_bytes&#39;: 112, &#39;per_entry_bytes&#39;: 56}</span></span></span></code></pre></div><hr>
<h2 id="設計權衡">設計權衡</h2>
<h3 id="__slots__-vs-標準類別"><code>__slots__</code> vs 標準類別</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>標準類別</th>
          <th><code>__slots__</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>記憶體佔用</strong></td>
          <td>較多（有 <code>__dict__</code>）</td>
          <td>較少（節省 ~60-70%）</td>
      </tr>
      <tr>
          <td><strong>動態屬性</strong></td>
          <td>支援 <code>obj.new_attr = x</code></td>
          <td>不支援（除非加 <code>__dict__</code>）</td>
      </tr>
      <tr>
          <td><strong>繼承</strong></td>
          <td>簡單</td>
          <td>子類別需要自己的 <code>__slots__</code></td>
      </tr>
      <tr>
          <td><strong>弱引用</strong></td>
          <td>預設支援</td>
          <td>需要加入 <code>__weakref__</code> slot</td>
      </tr>
      <tr>
          <td><strong>Pickle</strong></td>
          <td>直接支援</td>
          <td>需要 <code>__getstate__</code>/<code>__setstate__</code></td>
      </tr>
      <tr>
          <td><strong>多重繼承</strong></td>
          <td>正常運作</td>
          <td>多個父類別不能都有非空 <code>__slots__</code></td>
      </tr>
  </tbody>
</table>
<h3 id="強引用-vs-弱引用快取">強引用 vs 弱引用快取</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>強引用快取</th>
          <th>弱引用快取</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>記憶體管理</strong></td>
          <td>需要手動清理</td>
          <td>自動清理</td>
      </tr>
      <tr>
          <td><strong>資料保證</strong></td>
          <td>資料一定存在</td>
          <td>資料可能被回收</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>關鍵配置</td>
          <td>暫時性資料</td>
      </tr>
      <tr>
          <td><strong>實作複雜度</strong></td>
          <td>簡單</td>
          <td>稍微複雜</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用哪種技術">何時使用哪種技術？</h3>





<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">決策樹：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">物件數量多嗎？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 是 → 考慮 __slots__
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   └── 需要動態屬性嗎？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│       ├── 是 → __slots__ = [..., &#39;__dict__&#39;]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│       └── 否 → __slots__ = [...]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">└── 否 → 標準類別即可
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">快取可能無限增長嗎？
</span></span><span class="line"><span class="ln">11</span><span class="cl">├── 是 → 使用 WeakValueDictionary 或 LRU
</span></span><span class="line"><span class="ln">12</span><span class="cl">└── 否 → 普通字典即可
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">資料可以被回收嗎？
</span></span><span class="line"><span class="ln">15</span><span class="cl">├── 是 → weakref
</span></span><span class="line"><span class="ln">16</span><span class="cl">└── 否 → 強引用</span></span></code></pre></div><hr>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>建立大量小物件</strong>：如資料點、事件、配置項目</li>
<li><strong>記憶體使用是瓶頸</strong>：經過 profiling 確認</li>
<li><strong>快取可能無限增長</strong>：如用戶 session、請求資料</li>
<li><strong>長時間運行的服務</strong>：如 web server、daemon</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>物件數量很少</strong>：優化效果不明顯</li>
<li><strong>需要動態新增屬性</strong>：<code>__slots__</code> 會限制彈性</li>
<li><strong>過早優化</strong>：先確認是否真的有問題</li>
<li><strong>程式碼可讀性優先</strong>：標準類別更直觀</li>
</ul>
<h3 id="優化決策流程">優化決策流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Step 1: Profile first!</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># Don&#39;t optimize until you know where the problem is</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># ... run your code ...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">snapshot</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">top_stats</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="n">statistics</span><span class="p">(</span><span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">top_stats</span><span class="p">[:</span><span class="mi">10</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">stat</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Step 2: If memory is the issue, identify the class</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Look for classes with many instances</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">Counter</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">counter</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span> <span class="k">for</span> <span class="n">obj</span> <span class="ow">in</span> <span class="n">gc</span><span class="o">.</span><span class="n">get_objects</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">counter</span><span class="o">.</span><span class="n">most_common</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># Step 3: Only then apply __slots__ to hot classes</span></span></span></code></pre></div><hr>
<h2 id="練習">練習</h2>
<h3 id="基礎練習比較有無-__slots__-的記憶體差異">基礎練習：比較有無 <code>__slots__</code> 的記憶體差異</h3>
<p>撰寫一個腳本，比較以下三種類別建立 100,000 個實例的記憶體使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Complete this benchmark script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</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"># 1. Standard class</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">PointStandard</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 2. Class with __slots__</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">class</span> <span class="nc">PointSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 3. Named tuple</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">PointNamed</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">&#39;PointNamed&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># TODO: Write benchmark function</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">100000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure memory for creating `count` instances&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># Implement this</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># TODO: Compare results</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># Expected: PointSlots uses ~3x less memory than PointStandard</span></span></span></code></pre></div><h3 id="進階練習實作-weakref-快取">進階練習：實作 weakref 快取</h3>
<p>建立一個 <code>ImageCache</code> 類別，具有以下功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Implement ImageCache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ImageCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Cache for image data with automatic cleanup.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Requirements:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - Use WeakValueDictionary for auto-cleanup
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - Track hit/miss statistics
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - Support maximum size limit
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - Provide memory usage estimation
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Example usage:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        cache = ImageCache(max_size=100)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        img = cache.get_or_load(&#34;photo.jpg&#34;, lambda: load_image(&#34;photo.jpg&#34;))
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        print(cache.stats())
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        # {&#39;hits&#39;: 0, &#39;misses&#39;: 1, &#39;size&#39;: 1}
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_size</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># TODO: Initialize cache</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">get_or_load</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">loader</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># TODO: Implement get or load logic</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># TODO: Return cache statistics</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題用-tracemalloc-追蹤記憶體洩漏">挑戰題：用 tracemalloc 追蹤記憶體洩漏</h3>
<p>給定以下有記憶體洩漏的程式碼，使用 <code>tracemalloc</code> 找出問題並修復：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Find and fix the memory leak</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">EventHandler</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">_handlers</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># Class variable - potential leak!</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">EventHandler</span><span class="o">.</span><span class="n">_handlers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>  <span class="c1"># Leak: strong reference</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">callback</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">callback</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">fire</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">for</span> <span class="n">cb</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">cb</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">process_events</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;This function creates handlers but never cleans them up&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">handler</span> <span class="o">=</span> <span class="n">EventHandler</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;handler_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">handler</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">handler</span><span class="o">.</span><span class="n">fire</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;event_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># handler goes out of scope but is still in _handlers!</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># TODO:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 1. Use tracemalloc to measure memory growth</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 2. Identify the leak</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 3. Fix EventHandler to use weak references</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># 4. Verify the fix with tracemalloc</span></span></span></code></pre></div><hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#slots"><code>__slots__</code> 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/weakref.html">weakref 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/tracemalloc.html">tracemalloc 官方文件</a></li>
<li><a href="https://pympler.readthedocs.io/">Pympler - Memory profiling</a></li>
<li><a href="https://realpython.com/python-memory-management/">Python Memory Management - Real Python</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/04-cpython-internals/case-studies/profiling/" data-link-title="案例：效能分析實戰" data-link-desc="用 cProfile 和 line_profiler 分析 Markdown 連結檢查器的效能瓶頸">效能分析實戰</a></em>
<em>返回：<a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制</a></em></p>
]]></content:encoded></item><item><title>案例：插件架構設計</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的驗證邏輯直接寫在類別中：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&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="s2">&amp;#34;&amp;#34;&amp;#34;驗證單個 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 讀取檔案 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&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">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&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="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&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="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查命名規範&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查共用模組導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查輸出格式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查測試檔案是否存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>所有邏輯集中管理，容易找到相關程式碼&lt;/li>
&lt;li>沒有額外的抽象層，直接明瞭&lt;/li>
&lt;li>對小型專案來說足夠使用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要擴展時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>第三方無法新增自己的檢查項&lt;/strong>：想加入「Docstring 檢查」必須修改 &lt;code>HookValidator&lt;/code>&lt;/li>
&lt;li>&lt;strong>修改需要改動核心程式碼&lt;/strong>：每新增一個檢查都要改 &lt;code>validate_hook&lt;/code> 方法&lt;/li>
&lt;li>&lt;strong>違反開放封閉原則&lt;/strong>：對擴展不開放，對修改不封閉&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>定義清晰的插件介面&lt;/strong>：用 Protocol 描述檢查項該有的行為&lt;/li>
&lt;li>&lt;strong>支援第三方擴展&lt;/strong>：外部套件可以新增檢查項&lt;/li>
&lt;li>&lt;strong>插件可以獨立測試&lt;/strong>：每個檢查項是獨立的單元&lt;/li>
&lt;li>&lt;strong>支援插件的啟用/停用&lt;/strong>：可以動態控制要執行哪些檢查&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面&lt;/h4>
&lt;p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">runtime_checkable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation issue description&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&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">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CheckContext&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Context passed to each check&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nd">@runtime_checkable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationCheck&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> Protocol for validation checks
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class implementing this protocol can be used as a validation check.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> No inheritance required - just implement the methods.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check name for identification&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">description&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Human-readable description&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">CheckContext&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> Execute the validation check
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> context: Check context with file info
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> List of validation issues found
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？&lt;/h5>
&lt;ul>
&lt;li>Protocol 是「鴨子型別」的靜態版本&lt;/li>
&lt;li>不強制繼承，現有類別只要有對應方法就能用&lt;/li>
&lt;li>&lt;code>@runtime_checkable&lt;/code> 讓我們可以用 &lt;code>isinstance()&lt;/code> 檢查&lt;/li>
&lt;/ul>
&lt;h4 id="步驟-2實作插件註冊機制">步驟 2：實作插件註冊機制&lt;/h4>
&lt;p>註冊機制提供兩種方式：裝飾器和明確註冊：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的驗證邏輯直接寫在類別中：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># ... 讀取檔案 ...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 執行各項檢查 - 硬編碼的驗證規則</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">check_lib_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查共用模組導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">check_output_format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查輸出格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查測試檔案是否存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li>所有邏輯集中管理，容易找到相關程式碼</li>
<li>沒有額外的抽象層，直接明瞭</li>
<li>對小型專案來說足夠使用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要擴展時：</p>
<ul>
<li><strong>第三方無法新增自己的檢查項</strong>：想加入「Docstring 檢查」必須修改 <code>HookValidator</code></li>
<li><strong>修改需要改動核心程式碼</strong>：每新增一個檢查都要改 <code>validate_hook</code> 方法</li>
<li><strong>違反開放封閉原則</strong>：對擴展不開放，對修改不封閉</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>定義清晰的插件介面</strong>：用 Protocol 描述檢查項該有的行為</li>
<li><strong>支援第三方擴展</strong>：外部套件可以新增檢查項</li>
<li><strong>插件可以獨立測試</strong>：每個檢查項是獨立的單元</li>
<li><strong>支援插件的啟用/停用</strong>：可以動態控制要執行哪些檢查</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面</h4>
<p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">project_root</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    Protocol for validation checks
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    Any class implementing this protocol can be used as a validation check.
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    No inheritance required - just implement the methods.
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        Execute the validation check
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">            context: Check context with file info
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            List of validation issues found
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？</h5>
<ul>
<li>Protocol 是「鴨子型別」的靜態版本</li>
<li>不強制繼承，現有類別只要有對應方法就能用</li>
<li><code>@runtime_checkable</code> 讓我們可以用 <code>isinstance()</code> 檢查</li>
</ul>
<h4 id="步驟-2實作插件註冊機制">步驟 2：實作插件註冊機制</h4>
<p>註冊機制提供兩種方式：裝飾器和明確註冊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Type variable for check classes</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">CheckRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Registry for validation checks
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Supports both decorator registration and explicit registration.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        Register a check instance
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">            check: Check instance to register
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            The same check instance (for chaining)
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            registry.register(NamingCheck())
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        Decorator for registering check classes
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">            name: Optional custom name (default: class name)
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            Class decorator
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            @registry.check()
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">            class MyCheck:
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">                ...
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="nf">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="k">def</span> <span class="nf">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">
</span></span><span class="line"><span class="ln">82</span><span class="cl"><span class="c1"># Global registry instance</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span></span></span></code></pre></div><p><strong>兩種註冊方式的比較</strong>：</p>
<table>
  <thead>
      <tr>
          <th>方式</th>
          <th>使用時機</th>
          <th>優點</th>
          <th>缺點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>裝飾器 <code>@registry.check()</code></td>
          <td>類別定義時</td>
          <td>簡潔、宣告式</td>
          <td>模組載入時就註冊</td>
      </tr>
      <tr>
          <td>明確註冊 <code>registry.register()</code></td>
          <td>執行時</td>
          <td>更靈活、可延遲</td>
          <td>較囉嗦</td>
      </tr>
  </tbody>
</table>
<h4 id="步驟-3支援-entry_points-自動發現">步驟 3：支援 entry_points 自動發現</h4>
<p><code>entry_points</code> 是 Python 打包系統的標準機制，讓外部套件可以註冊插件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Iterator</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Discover checks from installed packages via entry_points
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        group: Entry point group name
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Yields:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Discovered check instances
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        for check in discover_checks():
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            registry.register(check)
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># Python 3.10+ API</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># Python 3.9 compatibility</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="c1"># Load the check class/factory</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="c1"># Create instance</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="c1"># Validate it implements the protocol</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="k">def</span> <span class="nf">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">    Load all external checks into a registry
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        registry: Target registry
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="s2">        Number of checks loaded
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">return</span> <span class="n">count</span></span></span></code></pre></div><h4 id="步驟-4實作插件載入與管理">步驟 4：實作插件載入與管理</h4>
<p>插件管理器整合了註冊、發現和執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate compliance status&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">    Plugin-based hook validator
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    Uses registered checks to validate hook files.
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="n">auto_discover</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        Initialize validator
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">            registry: Check registry (default: global registry)
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">            project_root: Project root directory
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">            auto_discover: Whether to auto-discover external checks
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">        Validate a single hook file
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">            hook_path: Path to hook file
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">            Validation result
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="c1"># Check file exists</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="c1"># Read content</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="c1"># Build context</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="c1"># Run all enabled checks</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">all_issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">return</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Plugin-based Hook Validator
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">A validation system using Protocol and registry pattern,
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">allowing third-party extensions via entry_points.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">Dict</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">Iterator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">List</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">Optional</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">Type</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"># Data Classes</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">class</span> <span class="nc">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">project_root</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="c1"># Protocol Definition</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Protocol for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Execute the validation check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="c1"># Registry</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">CheckRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Register a check instance&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Decorator for registering check classes&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="nf">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># Global registry</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"># Entry Points Discovery</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="k">def</span> <span class="nf">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Discover checks from installed packages via entry_points&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span> <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">)</span> <span class="k">else</span> <span class="n">check_factory</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#34;</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl"><span class="k">def</span> <span class="nf">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load all external checks into a registry&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># Built-in Checks</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">
</span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="k">class</span> <span class="nc">NamingConventionCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook file naming convention&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl">    <span class="n">VALID_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/03-design-patterns/case-studies/plugin-architecture/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;naming_convention&#34;</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">
</span></span><span class="line"><span class="ln">193</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook files follow naming conventions&#34;</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="n">valid</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_PATTERNS</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Invalid file name: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake_case or kebab-case naming&#34;</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="k">class</span> <span class="nc">LibImportCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that hooks import required libraries&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;lib_import&#34;</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks import hook_io module&#34;</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">has_import</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">has_import</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_io import read_hook_input, write_hook_output&#34;</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">
</span></span><span class="line"><span class="ln">251</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl"><span class="k">class</span> <span class="nc">OutputFormatCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook output format&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">GOOD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="n">BAD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;output_format&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks use proper output functions&#34;</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">has_bad</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">BAD_PATTERNS</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="k">if</span> <span class="n">has_bad</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using print(json.dumps(...)) instead of write_hook_output()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use write_hook_output() for proper output formatting&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">
</span></span><span class="line"><span class="ln">293</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">
</span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="k">class</span> <span class="nc">TestExistsCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that corresponding test file exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;test_exists&#34;</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a corresponding test file&#34;</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">
</span></span><span class="line"><span class="ln">307</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">        <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">possible_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="n">context</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">            <span class="n">context</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_paths</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;No test file found: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Create test at .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl"><span class="c1"># Validator</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Plugin-based hook validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">        <span class="n">auto_discover</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">344</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">        <span class="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">
</span></span><span class="line"><span class="ln">354</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">355</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">
</span></span><span class="line"><span class="ln">365</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">373</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">377</span><span class="cl">
</span></span><span class="line"><span class="ln">378</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="n">all_issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">        <span class="n">checks_run</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">386</span><span class="cl">
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">392</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">393</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">395</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">396</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">397</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">398</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">401</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">402</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">403</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</span>
</span></span><span class="line"><span class="ln">404</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">405</span><span class="cl">
</span></span><span class="line"><span class="ln">406</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">407</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate all hook files in a directory&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">408</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">409</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">410</span><span class="cl">
</span></span><span class="line"><span class="ln">411</span><span class="cl">        <span class="n">hooks_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">412</span><span class="cl">
</span></span><span class="line"><span class="ln">413</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">414</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">415</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">416</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hooks_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">417</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">418</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">419</span><span class="cl">                            <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">420</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hooks directory not found: </span><span class="si">{</span><span class="n">hooks_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">421</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">422</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">423</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">424</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">425</span><span class="cl">
</span></span><span class="line"><span class="ln">426</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">427</span><span class="cl">        <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">428</span><span class="cl">            <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">429</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">430</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">431</span><span class="cl">
</span></span><span class="line"><span class="ln">432</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">433</span><span class="cl">
</span></span><span class="line"><span class="ln">434</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">435</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">436</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">437</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">438</span><span class="cl">
</span></span><span class="line"><span class="ln">439</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">440</span><span class="cl"><span class="c1"># Demo</span>
</span></span><span class="line"><span class="ln">441</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">442</span><span class="cl">
</span></span><span class="line"><span class="ln">443</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">444</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">445</span><span class="cl">
</span></span><span class="line"><span class="ln">446</span><span class="cl">    <span class="c1"># Show registered checks</span>
</span></span><span class="line"><span class="ln">447</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Registered checks:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">448</span><span class="cl">    <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">449</span><span class="cl">        <span class="n">check</span> <span class="o">=</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">get_check</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">450</span><span class="cl">        <span class="k">if</span> <span class="n">check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">451</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">description</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">452</span><span class="cl">
</span></span><span class="line"><span class="ln">453</span><span class="cl">    <span class="c1"># Create test hook</span>
</span></span><span class="line"><span class="ln">454</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">455</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span>
</span></span><span class="line"><span class="ln">456</span><span class="cl">        <span class="n">hooks_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">457</span><span class="cl">
</span></span><span class="line"><span class="ln">458</span><span class="cl">        <span class="c1"># Create a hook file</span>
</span></span><span class="line"><span class="ln">459</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">hooks_dir</span> <span class="o">/</span> <span class="s2">&#34;check-permissions.py&#34;</span>
</span></span><span class="line"><span class="ln">460</span><span class="cl">        <span class="n">hook_file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">461</span><span class="cl"><span class="s2">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">462</span><span class="cl"><span class="s2">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">463</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">464</span><span class="cl"><span class="s2">def main():
</span></span></span><span class="line"><span class="ln">465</span><span class="cl"><span class="s2">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">466</span><span class="cl"><span class="s2">    write_hook_output({&#34;decision&#34;: &#34;allow&#34;})
</span></span></span><span class="line"><span class="ln">467</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">468</span><span class="cl"><span class="s2">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">469</span><span class="cl"><span class="s2">    main()
</span></span></span><span class="line"><span class="ln">470</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">471</span><span class="cl">
</span></span><span class="line"><span class="ln">472</span><span class="cl">        <span class="c1"># Validate</span>
</span></span><span class="line"><span class="ln">473</span><span class="cl">        <span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">project_root</span><span class="o">=</span><span class="n">tmpdir</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">474</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">475</span><span class="cl">
</span></span><span class="line"><span class="ln">476</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Validation result for </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">477</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Compliant: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">478</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Checks run: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">checks_run</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">479</span><span class="cl">        <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">480</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="建立插件">建立插件</h4>
<p>建立自訂檢查項只需要實作 Protocol 定義的介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">DocstringCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    Check that hooks have proper docstrings
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    A custom check that can be added to the validator.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    No inheritance required - just implement the protocol.
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Check for module docstring</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">docstring_pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;^(#!/.*\n)?[\s]*[&#34;</span><span class="se">\&#39;</span><span class="s1">\]</span><span class="si">{3}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">docstring_pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing module docstring&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s1">&#39;Add a docstring at the top: &#34;&#34;&#34;Description of hook&#34;&#34;&#34;&#39;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">class</span> <span class="nc">SecurityCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    Check for potential security issues
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    Looks for dangerous patterns like eval(), exec(), subprocess with shell=True
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">DANGEROUS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\beval\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;eval() is dangerous, consider alternatives&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\bexec\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;exec() is dangerous, consider alternatives&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;subprocess.*shell\s*=\s*True&#39;</span><span class="p">,</span> <span class="s2">&#34;shell=True is a security risk&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="sa">r</span><span class="s1">&#39;os\.system\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;os.system() is insecure, use subprocess instead&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;security&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for potential security vulnerabilities&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">message</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">DANGEROUS_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="n">message</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h4 id="註冊插件">註冊插件</h4>
<p>有三種方式可以註冊插件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Method 1: Using decorator (at class definition time)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">MyCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;my_check&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># Method 2: Explicit registration (at runtime)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">docstring_check</span> <span class="o">=</span> <span class="n">DocstringCheck</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">security_check</span> <span class="o">=</span> <span class="n">SecurityCheck</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">docstring_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">security_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># Method 3: Create custom registry</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">custom_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">DocstringCheck</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">SecurityCheck</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">registry</span><span class="o">=</span><span class="n">custom_registry</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></span></span></code></pre></div><h4 id="使用-entry_points">使用 entry_points</h4>
<p>對於要發布為獨立套件的插件，使用 <code>pyproject.toml</code> 設定 entry_points：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># pyproject.toml</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-hook-checks&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Custom hook validation checks&#34;</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="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c"># hook-validator is the core package</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">entry-points</span><span class="p">.</span><span class="s2">&#34;hook_validator.checks&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># Format: name = &#34;module:factory_or_class&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">docstring</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks:DocstringCheck&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">security</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks.security:SecurityCheck&#34;</span></span></span></code></pre></div><p>插件模組結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># my_hook_checks/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">.docstring</span> <span class="kn">import</span> <span class="n">DocstringCheck</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">.security</span> <span class="kn">import</span> <span class="n">SecurityCheck</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;DocstringCheck&#34;</span><span class="p">,</span> <span class="s2">&#34;SecurityCheck&#34;</span><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"># my_hook_checks/docstring.py</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">DocstringCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for module docstrings&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># ... implementation ...</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span></span></span></code></pre></div><p>安裝後，<code>PluginValidator</code> 會自動發現並載入這些檢查項：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Automatic discovery from entry_points</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">auto_discover</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Check what&#39;s loaded</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># [&#39;naming_convention&#39;, &#39;lib_import&#39;, &#39;output_format&#39;, &#39;test_exists&#39;,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">#  &#39;docstring&#39;, &#39;security&#39;]  # &lt;- external checks discovered!</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>硬編碼方法</th>
          <th>插件架構</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>擴展性</strong></td>
          <td>差：需修改核心程式碼</td>
          <td>優秀：第三方可自由擴展</td>
      </tr>
      <tr>
          <td><strong>初始複雜度</strong></td>
          <td>低：直接寫邏輯</td>
          <td>中：需要理解 Protocol 和註冊機制</td>
      </tr>
      <tr>
          <td><strong>維護成本</strong></td>
          <td>隨功能增加而上升</td>
          <td>穩定：新增功能不改核心</td>
      </tr>
      <tr>
          <td><strong>第三方擴展</strong></td>
          <td>不支援</td>
          <td>支援：透過 entry_points</td>
      </tr>
      <tr>
          <td><strong>測試難度</strong></td>
          <td>需要 mock 整個類別</td>
          <td>容易：每個 check 獨立測試</td>
      </tr>
      <tr>
          <td><strong>執行時彈性</strong></td>
          <td>固定</td>
          <td>高：可動態啟用/停用</td>
      </tr>
      <tr>
          <td><strong>除錯難度</strong></td>
          <td>簡單：程式碼集中</td>
          <td>需要追蹤插件來源</td>
      </tr>
      <tr>
          <td><strong>效能開銷</strong></td>
          <td>無</td>
          <td>輕微：註冊和迭代成本</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要支援第三方擴展的工具（如 pytest、flake8、pre-commit）</li>
<li>功能模組化明確，各檢查項獨立</li>
<li>預期會頻繁新增功能</li>
<li>需要讓使用者自訂行為</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>內部使用的小工具</li>
<li>功能很少變動</li>
<li>團隊對 Protocol 和 entry_points 不熟悉</li>
<li>效能關鍵的熱路徑</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習實作格式檢查插件">基礎練習：實作格式檢查插件</h3>
<p>實作一個 <code>IndentationCheck</code>，檢查 Python 檔案是否使用一致的縮排（空格 vs Tab）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">IndentationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for consistent indentation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;indentation&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for consistent indentation style&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 1. Check each line&#39;s leading whitespace</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># 2. Detect if mixing tabs and spaces</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 3. Report as warning if inconsistent</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>context.content.splitlines()</code> 取得每一行</li>
<li>檢查每行開頭的空白字元 <code>line[:len(line) - len(line.lstrip())]</code></li>
</ul>
<h3 id="進階練習新增優先順序和相依性">進階練習：新增優先順序和相依性</h3>
<p>擴展 <code>ValidationCheck</code> Protocol，支援：</p>
<ol>
<li><strong>優先順序</strong>：決定檢查項執行順序</li>
<li><strong>相依性</strong>：某些檢查必須在其他檢查之後執行</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">name</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">priority</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Lower number = higher priority (default: 100)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">depends_on</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Names of checks that must run before this one&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span> <span class="o">...</span></span></span></code></pre></div><p>然後修改 <code>PluginValidator.validate_hook()</code> 使用拓撲排序執行檢查。</p>
<h3 id="挑戰題實作插件的熱載入">挑戰題：實作插件的熱載入</h3>
<p>實作「不重啟程式就能載入新插件」的功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HotReloadableRegistry</span><span class="p">(</span><span class="n">CheckRegistry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry that supports hot-reloading plugins&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">watch_directory</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Watch a directory for new plugin files&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># TODO: Use watchdog or polling to detect new files</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">reload_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Reload a specific check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># TODO: Unload old module, load new version</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>importlib.reload()</code> 重新載入模組</li>
<li>使用 <code>watchdog</code> 套件監控檔案變化</li>
<li>注意處理模組快取 <code>sys.modules</code></li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Protocol">Python typing.Protocol</a></li>
<li><a href="https://docs.python.org/3/library/importlib.metadata.html#entry-points">importlib.metadata entry_points</a></li>
<li><a href="https://docs.pytest.org/en/stable/how-to/writing_plugins.html">pytest 插件系統</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools entry_points</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em></p>
]]></content:encoded></item><item><title>T.C3 ANSI parser 測試資料不覆蓋真實 shell output</title><link>https://tarrragon.github.io/blog/testing/cases/ansi-parser-test-data-blindspot/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/ansi-parser-test-data-blindspot/</guid><description>&lt;p>這個案例的核心責任是說明 unit test 的輸入資料品質如何決定測試的有效性。Parser 邏輯正確、斷言正確、覆蓋率高 — 但測試資料是人工挑選的乾淨子集，跟真實環境的輸入分佈不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 的 &lt;code>AnsiParser&lt;/code> 負責解析終端機輸出的 ANSI escape 序列，轉換為帶色彩的文字 token。unit test 用手寫字串驗證：&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">// 測試資料範例 — 乾淨的 SGR 色彩碼
&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="n">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;紅色文字&amp;#39;&lt;/span>&lt;span class="p">,&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">3&lt;/span>&lt;span class="cl"> &lt;span class="kd">final&lt;/span> &lt;span class="n">tokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="se">\x1B&lt;/span>&lt;span class="s1">[31mhello&lt;/span>&lt;span class="se">\x1B&lt;/span>&lt;span class="s1">[0m&amp;#39;&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">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tokens&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">isA&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">TextToken&lt;/span>&lt;span class="o">&amp;gt;&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;/code>&lt;/pre>&lt;/div>&lt;p>真實 zsh prompt 啟動後送出的控制序列（擷取自實機 log）：&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">\x1B]0;user@host: ~\x07 ← OSC：設定終端機視窗標題
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">\x1B[?2004h ← CSI private mode：啟用括號貼上模式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">\x1B[?1h ← CSI private mode：啟用應用程式游標鍵
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">\x1B(B ← 字元集指定：選擇 ASCII
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">\x1B[?25l ← CSI private mode：隱藏游標&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Parser 只認識 &lt;code>\x1B[{數字;數字}{字母}&lt;/code> 格式的標準 CSI，其他全部殘留在輸出中。&lt;/p>
&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>18 個 AnsiParser test，全過&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試覆蓋的序列類型&lt;/td>
 &lt;td>SGR 色彩碼（&lt;code>\x1B[31m&lt;/code> 等）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>真實環境的序列類型&lt;/td>
 &lt;td>SGR + OSC + CSI private mode + 字元集指定 + 其他&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實機表現&lt;/td>
 &lt;td>終端機畫面散佈 &lt;code>]0;user@host&lt;/code> 等亂碼片段&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復&lt;/td>
 &lt;td>新增 3 個 RegExp 過濾 OSC / CSI private / 其他 escape&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>測試資料的代表性是隱性假設&lt;/strong>。18 個 test 的斷言都正確 — &lt;code>\x1B[31m&lt;/code> 確實應該產生紅色 token。但「測試輸入能代表真實輸入」是一個未經驗證的假設。真實 zsh 的輸出包含 5+ 種 escape 序列類型，測試只覆蓋了 1 種。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Parser 的行為對未知序列是「透傳」而非「報錯」&lt;/strong>。這是合理的設計 — 不認識的序列不應該讓 parser 崩潰。但透傳的後果是亂碼靜默出現在畫面上，不觸發任何錯誤或 log，開發者在 unit test 環境完全不會察覺。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>手寫測試資料 vs 錄製真實資料&lt;/strong>。如果測試資料是從真實 shell session 錄製的（capture 一次真實 zsh 啟動輸出），OSC 和 CSI private mode 會自然出現在測試輸入中，parser 的透傳行為會在 test 階段就被看到。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>從真實環境錄製測試資料&lt;/strong>：用 &lt;code>script&lt;/code> 命令或 WebSocket log 錄一次真實 shell session 的完整輸出，作為 integration test 的輸入。即使不改 parser 邏輯，至少能看到「哪些序列被透傳了」。&lt;/li>
&lt;li>&lt;strong>Parser 對未知序列記 warning log&lt;/strong>：透傳是合理的 fallback，但加一行 &lt;code>developer.log('Unknown escape: ${escape.codeUnits}')&lt;/code> 讓開發者知道有未處理的序列。&lt;/li>
&lt;li>&lt;strong>測試分兩類&lt;/strong>：「功能正確性」用手寫乾淨字串；「環境相容性」用錄製的真實輸出。兩類測試回答不同問題。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想理解測試資料代表性 → &lt;a href="https://tarrragon.github.io/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性&lt;/a>&lt;/li>
&lt;li>想建 protocol integration test 用真實 ttyd 輸出 → &lt;a href="https://tarrragon.github.io/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試&lt;/a>&lt;/li>
&lt;li>類似案例（mock 遮蔽） → &lt;a href="https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type mock 遮蔽&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 unit test 的輸入資料品質如何決定測試的有效性。Parser 邏輯正確、斷言正確、覆蓋率高 — 但測試資料是人工挑選的乾淨子集，跟真實環境的輸入分佈不同。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 的 <code>AnsiParser</code> 負責解析終端機輸出的 ANSI escape 序列，轉換為帶色彩的文字 token。unit 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">// 測試資料範例 — 乾淨的 SGR 色彩碼
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="n">test</span><span class="p">(</span><span class="s1">&#39;紅色文字&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kd">final</span> <span class="n">tokens</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\x1B</span><span class="s1">[31mhello</span><span class="se">\x1B</span><span class="s1">[0m&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="n">expect</span><span class="p">(</span><span class="n">tokens</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">isA</span><span class="o">&lt;</span><span class="n">TextToken</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>真實 zsh prompt 啟動後送出的控制序列（擷取自實機 log）：</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">\x1B]0;user@host: ~\x07          ← OSC：設定終端機視窗標題
</span></span><span class="line"><span class="ln">2</span><span class="cl">\x1B[?2004h                      ← CSI private mode：啟用括號貼上模式
</span></span><span class="line"><span class="ln">3</span><span class="cl">\x1B[?1h                         ← CSI private mode：啟用應用程式游標鍵
</span></span><span class="line"><span class="ln">4</span><span class="cl">\x1B(B                           ← 字元集指定：選擇 ASCII
</span></span><span class="line"><span class="ln">5</span><span class="cl">\x1B[?25l                        ← CSI private mode：隱藏游標</span></span></code></pre></div><p>Parser 只認識 <code>\x1B[{數字;數字}{字母}</code> 格式的標準 CSI，其他全部殘留在輸出中。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>測試案例數</td>
          <td>18 個 AnsiParser test，全過</td>
      </tr>
      <tr>
          <td>測試覆蓋的序列類型</td>
          <td>SGR 色彩碼（<code>\x1B[31m</code> 等）</td>
      </tr>
      <tr>
          <td>真實環境的序列類型</td>
          <td>SGR + OSC + CSI private mode + 字元集指定 + 其他</td>
      </tr>
      <tr>
          <td>實機表現</td>
          <td>終端機畫面散佈 <code>]0;user@host</code> 等亂碼片段</td>
      </tr>
      <tr>
          <td>修復</td>
          <td>新增 3 個 RegExp 過濾 OSC / CSI private / 其他 escape</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>測試資料的代表性是隱性假設</strong>。18 個 test 的斷言都正確 — <code>\x1B[31m</code> 確實應該產生紅色 token。但「測試輸入能代表真實輸入」是一個未經驗證的假設。真實 zsh 的輸出包含 5+ 種 escape 序列類型，測試只覆蓋了 1 種。</p>
</li>
<li>
<p><strong>Parser 的行為對未知序列是「透傳」而非「報錯」</strong>。這是合理的設計 — 不認識的序列不應該讓 parser 崩潰。但透傳的後果是亂碼靜默出現在畫面上，不觸發任何錯誤或 log，開發者在 unit test 環境完全不會察覺。</p>
</li>
<li>
<p><strong>手寫測試資料 vs 錄製真實資料</strong>。如果測試資料是從真實 shell session 錄製的（capture 一次真實 zsh 啟動輸出），OSC 和 CSI private mode 會自然出現在測試輸入中，parser 的透傳行為會在 test 階段就被看到。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>從真實環境錄製測試資料</strong>：用 <code>script</code> 命令或 WebSocket log 錄一次真實 shell session 的完整輸出，作為 integration test 的輸入。即使不改 parser 邏輯，至少能看到「哪些序列被透傳了」。</li>
<li><strong>Parser 對未知序列記 warning log</strong>：透傳是合理的 fallback，但加一行 <code>developer.log('Unknown escape: ${escape.codeUnits}')</code> 讓開發者知道有未處理的序列。</li>
<li><strong>測試分兩類</strong>：「功能正確性」用手寫乾淨字串；「環境相容性」用錄製的真實輸出。兩類測試回答不同問題。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解測試資料代表性 → <a href="/blog/testing/05-test-design-judgment/test-data-representativeness/" data-link-title="Test data 代表性" data-link-desc="手寫 vs 錄製 vs 生成三種測試資料來源 — 測試資料的代表性是一個隱性假設，決定了 test 能發現什麼問題">Test data 代表性</a></li>
<li>想建 protocol integration test 用真實 ttyd 輸出 → <a href="/blog/testing/03-protocol-integration-test/" data-link-title="模組三：協議整合測試" data-link-desc="對真實服務驗證 WebSocket / gRPC / HTTP 協議契約 — unit test 和 E2E test 之間的一層">模組三：協議整合測試</a></li>
<li>類似案例（mock 遮蔽） → <a href="/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type mock 遮蔽</a></li>
</ul>
]]></content:encoded></item><item><title>U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField</title><link>https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/</guid><description>&lt;p>這個案例的核心責任是說明輸入機制是設計產物（在企劃階段決定），不是實作細節（在寫 code 時順便加）。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 的 Terminal 畫面在 W2 修復前沒有任何文字輸入元件。使用者只能透過底部工具列的特殊鍵（Esc/Tab/Ctrl/方向鍵）操作終端機，無法打字。&lt;/p>
&lt;p>W2-001 修復時加入的 &lt;code>TextField&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="n">TextField&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="nl">keyboardType:&lt;/span> &lt;span class="n">TextInputType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">visiblePassword&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 class="nl">enableSuggestions:&lt;/span> &lt;span class="kc">false&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">4&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">autocorrect:&lt;/span> &lt;span class="kc">false&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">5&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">enableIMEPersonalizedLearning:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 關閉 IME 個人化學習
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">onSubmitted:&lt;/span> &lt;span class="n">_submitInput&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Enter 送出整行
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nl">textInputAction:&lt;/span> &lt;span class="n">TextInputAction&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">send&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">8&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每個參數都是一個設計決策，但沒有一個是事前規劃的 — 全部是寫 code 時臨時判斷。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設計決策&lt;/th>
 &lt;th>事前規劃&lt;/th>
 &lt;th>事後 hotfix 的風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>visiblePassword&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果用預設 &lt;code>text&lt;/code>，iOS 會自動校正 &lt;code>ls -la&lt;/code> 成其他東西&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>enableSuggestions: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>建議列遮擋終端機畫面下方&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>autocorrect: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>路徑 &lt;code>/usr/bin/&lt;/code> 可能被校正&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>enableIMEPersonalizedLearning: false&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>CLI 輸入含密碼和路徑，IME 學習是安全風險&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>onSubmitted&lt;/code>（整行送出）&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果逐字元送出，Tab 補全和命令編輯需要完全不同的 protocol 設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>TextInputAction.send&lt;/code>&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;td>如果用 &lt;code>newline&lt;/code>，使用者按 Enter 會換行不送出&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>輸入設計影響 UI layout 和 protocol&lt;/strong>。&lt;code>onSubmitted&lt;/code>（整行送出）vs 逐字元即時送出不只是 UI 問題 — 整行送出代表 protocol 層送的是 &lt;code>command\n&lt;/code>，逐字元送出代表每個按鍵都是一個 WS frame。這個決策應該在 protocol spec 階段就做，因為它影響 server 端的行為預期。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>IME 控制有安全意涵&lt;/strong>。&lt;code>enableIMEPersonalizedLearning: false&lt;/code> 不只是 UX 偏好 — CLI 輸入可能包含資料庫密碼、API key、伺服器路徑。IME 學習這些內容等於把 secret 存到了 IME 的詞庫裡，跨 app 可用。這是安全問題，不是 UX 問題。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>事後 hotfix 的六個參數每個都有 gotcha&lt;/strong>。如果這些決策在企劃階段做，可以寫成決策表並在 code review 時對照。事後 hotfix 時開發者可能漏掉其中一兩個（例如只加 &lt;code>autocorrect: false&lt;/code> 但忘了 &lt;code>enableIMEPersonalizedLearning: false&lt;/code>），漏掉的那個就成為安全漏洞。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>功能規格新增「輸入機制決策表」&lt;/strong>：keyboard type / submit model / IME policy / special keys 四個維度，每個列出選項和取捨理由。&lt;/li>
&lt;li>&lt;strong>輸入機制跟 protocol 一起設計&lt;/strong>：「整行送出」還是「逐字元」決定了 WS 訊框的設計，必須在 protocol spec 階段決定。&lt;/li>
&lt;li>&lt;strong>安全敏感參數強制列入 review checklist&lt;/strong>：&lt;code>enableIMEPersonalizedLearning&lt;/code>、&lt;code>autocorrect&lt;/code> 在處理 secret 的輸入框中是安全要求，不是可選項。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計 mobile 輸入機制 → &lt;a href="https://tarrragon.github.io/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表&lt;/a>&lt;/li>
&lt;li>想看 protocol 跟輸入的關聯 → &lt;a href="https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type&lt;/a>（sendData 的型別決策）&lt;/li>
&lt;li>想做安全審查 → 待補：CLI 輸入安全 checklist&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明輸入機制是設計產物（在企劃階段決定），不是實作細節（在寫 code 時順便加）。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 的 Terminal 畫面在 W2 修復前沒有任何文字輸入元件。使用者只能透過底部工具列的特殊鍵（Esc/Tab/Ctrl/方向鍵）操作終端機，無法打字。</p>
<p>W2-001 修復時加入的 <code>TextField</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="n">TextField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="p">.</span><span class="n">visiblePassword</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 class="nl">enableSuggestions:</span> <span class="kc">false</span><span class="p">,</span>                       <span class="c1">// 關閉建議列
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="nl">autocorrect:</span> <span class="kc">false</span><span class="p">,</span>                             <span class="c1">// 關閉自動校正
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="nl">enableIMEPersonalizedLearning:</span> <span class="kc">false</span><span class="p">,</span>           <span class="c1">// 關閉 IME 個人化學習
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span>  <span class="nl">onSubmitted:</span> <span class="n">_submitInput</span><span class="p">,</span>                      <span class="c1">// Enter 送出整行
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span>  <span class="nl">textInputAction:</span> <span class="n">TextInputAction</span><span class="p">.</span><span class="n">send</span><span class="p">,</span>          <span class="c1">// 鍵盤顯示「傳送」
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">)</span></span></span></code></pre></div><p>每個參數都是一個設計決策，但沒有一個是事前規劃的 — 全部是寫 code 時臨時判斷。</p>
<table>
  <thead>
      <tr>
          <th>設計決策</th>
          <th>事前規劃</th>
          <th>事後 hotfix 的風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>visiblePassword</code></td>
          <td>沒有</td>
          <td>如果用預設 <code>text</code>，iOS 會自動校正 <code>ls -la</code> 成其他東西</td>
      </tr>
      <tr>
          <td><code>enableSuggestions: false</code></td>
          <td>沒有</td>
          <td>建議列遮擋終端機畫面下方</td>
      </tr>
      <tr>
          <td><code>autocorrect: false</code></td>
          <td>沒有</td>
          <td>路徑 <code>/usr/bin/</code> 可能被校正</td>
      </tr>
      <tr>
          <td><code>enableIMEPersonalizedLearning: false</code></td>
          <td>沒有</td>
          <td>CLI 輸入含密碼和路徑，IME 學習是安全風險</td>
      </tr>
      <tr>
          <td><code>onSubmitted</code>（整行送出）</td>
          <td>沒有</td>
          <td>如果逐字元送出，Tab 補全和命令編輯需要完全不同的 protocol 設計</td>
      </tr>
      <tr>
          <td><code>TextInputAction.send</code></td>
          <td>沒有</td>
          <td>如果用 <code>newline</code>，使用者按 Enter 會換行不送出</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>輸入設計影響 UI layout 和 protocol</strong>。<code>onSubmitted</code>（整行送出）vs 逐字元即時送出不只是 UI 問題 — 整行送出代表 protocol 層送的是 <code>command\n</code>，逐字元送出代表每個按鍵都是一個 WS frame。這個決策應該在 protocol spec 階段就做，因為它影響 server 端的行為預期。</p>
</li>
<li>
<p><strong>IME 控制有安全意涵</strong>。<code>enableIMEPersonalizedLearning: false</code> 不只是 UX 偏好 — CLI 輸入可能包含資料庫密碼、API key、伺服器路徑。IME 學習這些內容等於把 secret 存到了 IME 的詞庫裡，跨 app 可用。這是安全問題，不是 UX 問題。</p>
</li>
<li>
<p><strong>事後 hotfix 的六個參數每個都有 gotcha</strong>。如果這些決策在企劃階段做，可以寫成決策表並在 code review 時對照。事後 hotfix 時開發者可能漏掉其中一兩個（例如只加 <code>autocorrect: false</code> 但忘了 <code>enableIMEPersonalizedLearning: false</code>），漏掉的那個就成為安全漏洞。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>功能規格新增「輸入機制決策表」</strong>：keyboard type / submit model / IME policy / special keys 四個維度，每個列出選項和取捨理由。</li>
<li><strong>輸入機制跟 protocol 一起設計</strong>：「整行送出」還是「逐字元」決定了 WS 訊框的設計，必須在 protocol spec 階段決定。</li>
<li><strong>安全敏感參數強制列入 review checklist</strong>：<code>enableIMEPersonalizedLearning</code>、<code>autocorrect</code> 在處理 secret 的輸入框中是安全要求，不是可選項。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 mobile 輸入機制 → <a href="/blog/ux-design/03-input-mechanism/four-dimension-decision/" data-link-title="輸入機制決策表" data-link-desc="Keyboard type / submit model / IME policy / special keys 四個維度的決策框架 — 每個維度都是設計決策，影響 UI layout 和 protocol">輸入機制決策表</a></li>
<li>想看 protocol 跟輸入的關聯 → <a href="/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1 WS frame type</a>（sendData 的型別決策）</li>
<li>想做安全審查 → 待補：CLI 輸入安全 checklist</li>
</ul>
]]></content:encoded></item><item><title>9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/</guid><description>&lt;p>這個案例的核心責任是揭示「無明顯峰值但延遲就是收入」這類負載的容量設計、跟前兩個案例形成對照。金融交易不靠峰值定義成敗、靠每個交易的延遲穩定性 — 多 1ms 延遲在套利策略下可能直接吃掉整筆交易的利潤。Coinbase International Exchange 為這類負載做了一系列「反主流」的取捨：固定佈署、不啟用自動擴容、強制節點實體靠近。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Coinbase 在 2023-05 推出國際交易所、上線後關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Case Study&lt;/a>）：&lt;/p>
&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>100,000 messages/sec（擴容後）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲目標&lt;/td>
 &lt;td>sub-millisecond（次毫秒級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>累計交易額&lt;/td>
 &lt;td>上線以來超過 150 億美元&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>24/7、受監管的交易平台&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Amazon EC2 z1d 實例&lt;/strong>：高頻 CPU + NVMe 本地儲存、針對單執行緒效能最佳化&lt;/li>
&lt;li>&lt;strong>EC2 Cluster Placement Groups&lt;/strong>：強制把節點集中到單一機架附近、最小化 node-to-node 網路延遲&lt;/li>
&lt;li>&lt;strong>Amazon Aurora&lt;/strong>：高速 transaction lookup 的關聯式資料庫&lt;/li>
&lt;li>「Built from the ground up, using Cloud Native principles」（沒有複用既有交易所程式碼）&lt;/li>
&lt;li>內部使用 &lt;strong>RAFT consensus&lt;/strong> 維持交易順序&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最值得讀的地方、是它「沒有做」的事比「做了」的事更有教學價值。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>沒有用 Auto Scaling&lt;/strong>：交易撮合引擎用 RAFT consensus 維持嚴格順序、節點數量是 consensus 一部分、不能臨時增加。容量規劃完全是 &lt;em>pre-provision&lt;/em>、不是 reactive。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 必須區分「可水平擴容服務」跟「不可水平擴容服務」、後者的容量公式只有 headroom × peak、沒有 elastic 補救。&lt;/li>
&lt;li>&lt;strong>沒有用通用 EC2 實例&lt;/strong>：z1d 是 AWS 針對「高頻 CPU + NVMe」設計的特化實例、犧牲了通用性換取單核效能。這層選擇隱含一個容量規劃決策：&lt;em>單機效能上限&lt;/em> 直接決定 &lt;em>系統理論吞吐上限&lt;/em>、橫向擴容不能超過 RAFT 節點數限制、那麼縱向就必須榨乾。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 必須先判斷瓶頸屬「可分散」還是「不可分散」。&lt;/li>
&lt;li>&lt;strong>沒有用多區域分散&lt;/strong>：Cluster Placement Group 把節點壓到同一可用區內、犧牲了 region failover 速度、換取 node-to-node 網路延遲。這跟「高可用性」的常見直覺相反、是「延遲敏感型負載的容量設計優先於可靠性設計」的一個範例。&lt;/li>
&lt;li>&lt;strong>延遲是設計輸入、不是設計結果&lt;/strong>：sub-millisecond 是先訂目標、再反推所有架構選擇的結果、壓測只是驗證手段。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為&lt;/a> 中 Little&amp;rsquo;s Law 的反向應用 — 給定延遲目標 + 吞吐目標、反推 concurrency 上限 + 每個 stage 的 latency budget。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：「sub-millisecond latency 達成」這類陳述通常指 &lt;em>p50 或 p90&lt;/em>、不一定是 p99 或 p999。長尾延遲在 RAFT 系統下可能比平均高一個數量級（leader election、replication lag）。讀案例時要注意延遲分布 vs 平均值的差別。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>延遲敏感型服務先做 latency budget 反推&lt;/strong>：給每個 stage（網路、CPU、磁碟、序列化、共識）一個 latency 配額、總和等於 SLO 上限。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>。&lt;/li>
&lt;li>&lt;strong>單機效能榨乾優先於橫向擴容&lt;/strong>：當 consensus / ordered processing 限制了水平擴容時、單機選型（CPU 頻率、NUMA locality、NVMe）變成主要槓桿。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 把 saturation 點推得越遠。&lt;/li>
&lt;li>&lt;strong>拓樸感知的部署策略&lt;/strong>：Cluster Placement Group 是 AWS 名稱、概念是「網路拓樸感知的工作負載放置」。GCP 有 Compact Placement Policy、Azure 有 Proximity Placement Groups、自建 Kubernetes 有 Pod Topology Spread Constraints + Node Affinity。&lt;/li>
&lt;li>&lt;strong>接受「不可彈性」是有意識決策、不是失敗&lt;/strong>：很多服務不該全部都自動擴容。設計時要區分「需要 elastic 的 stateless 邊緣」跟「必須 pre-provision 的有狀態核心」、容量規劃也要兩條腿。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：所有主流雲端都有對應的高頻 CPU 實例（GCP C2 / Azure HBv 系列）、placement policy 與本地 NVMe 儲存。自建環境可以用 SR-IOV + RDMA + NUMA pinning 達成更極致的版本。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭示「無明顯峰值但延遲就是收入」這類負載的容量設計、跟前兩個案例形成對照。金融交易不靠峰值定義成敗、靠每個交易的延遲穩定性 — 多 1ms 延遲在套利策略下可能直接吃掉整筆交易的利潤。Coinbase International Exchange 為這類負載做了一系列「反主流」的取捨：固定佈署、不啟用自動擴容、強制節點實體靠近。</p>
<h2 id="觀察">觀察</h2>
<p>Coinbase 在 2023-05 推出國際交易所、上線後關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>吞吐量</td>
          <td>100,000 messages/sec（擴容後）</td>
      </tr>
      <tr>
          <td>延遲目標</td>
          <td>sub-millisecond（次毫秒級）</td>
      </tr>
      <tr>
          <td>累計交易額</td>
          <td>上線以來超過 150 億美元</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>24/7、受監管的交易平台</td>
      </tr>
  </tbody>
</table>
<p>服務組合：</p>
<ul>
<li><strong>Amazon EC2 z1d 實例</strong>：高頻 CPU + NVMe 本地儲存、針對單執行緒效能最佳化</li>
<li><strong>EC2 Cluster Placement Groups</strong>：強制把節點集中到單一機架附近、最小化 node-to-node 網路延遲</li>
<li><strong>Amazon Aurora</strong>：高速 transaction lookup 的關聯式資料庫</li>
<li>「Built from the ground up, using Cloud Native principles」（沒有複用既有交易所程式碼）</li>
<li>內部使用 <strong>RAFT consensus</strong> 維持交易順序</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>這個案例最值得讀的地方、是它「沒有做」的事比「做了」的事更有教學價值。</p>
<ol>
<li><strong>沒有用 Auto Scaling</strong>：交易撮合引擎用 RAFT consensus 維持嚴格順序、節點數量是 consensus 一部分、不能臨時增加。容量規劃完全是 <em>pre-provision</em>、不是 reactive。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 必須區分「可水平擴容服務」跟「不可水平擴容服務」、後者的容量公式只有 headroom × peak、沒有 elastic 補救。</li>
<li><strong>沒有用通用 EC2 實例</strong>：z1d 是 AWS 針對「高頻 CPU + NVMe」設計的特化實例、犧牲了通用性換取單核效能。這層選擇隱含一個容量規劃決策：<em>單機效能上限</em> 直接決定 <em>系統理論吞吐上限</em>、橫向擴容不能超過 RAFT 節點數限制、那麼縱向就必須榨乾。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 必須先判斷瓶頸屬「可分散」還是「不可分散」。</li>
<li><strong>沒有用多區域分散</strong>：Cluster Placement Group 把節點壓到同一可用區內、犧牲了 region failover 速度、換取 node-to-node 網路延遲。這跟「高可用性」的常見直覺相反、是「延遲敏感型負載的容量設計優先於可靠性設計」的一個範例。</li>
<li><strong>延遲是設計輸入、不是設計結果</strong>：sub-millisecond 是先訂目標、再反推所有架構選擇的結果、壓測只是驗證手段。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> 中 Little&rsquo;s Law 的反向應用 — 給定延遲目標 + 吞吐目標、反推 concurrency 上限 + 每個 stage 的 latency budget。</li>
</ol>
<p>需要警惕的判讀盲點：「sub-millisecond latency 達成」這類陳述通常指 <em>p50 或 p90</em>、不一定是 p99 或 p999。長尾延遲在 RAFT 系統下可能比平均高一個數量級（leader election、replication lag）。讀案例時要注意延遲分布 vs 平均值的差別。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>延遲敏感型服務先做 latency budget 反推</strong>：給每個 stage（網路、CPU、磁碟、序列化、共識）一個 latency 配額、總和等於 SLO 上限。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>。</li>
<li><strong>單機效能榨乾優先於橫向擴容</strong>：當 consensus / ordered processing 限制了水平擴容時、單機選型（CPU 頻率、NUMA locality、NVMe）變成主要槓桿。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 把 saturation 點推得越遠。</li>
<li><strong>拓樸感知的部署策略</strong>：Cluster Placement Group 是 AWS 名稱、概念是「網路拓樸感知的工作負載放置」。GCP 有 Compact Placement Policy、Azure 有 Proximity Placement Groups、自建 Kubernetes 有 Pod Topology Spread Constraints + Node Affinity。</li>
<li><strong>接受「不可彈性」是有意識決策、不是失敗</strong>：很多服務不該全部都自動擴容。設計時要區分「需要 elastic 的 stateless 邊緣」跟「必須 pre-provision 的有狀態核心」、容量規劃也要兩條腿。</li>
</ol>
<p>跨平台等效：所有主流雲端都有對應的高頻 CPU 實例（GCP C2 / Azure HBv 系列）、placement policy 與本地 NVMe 儲存。自建環境可以用 SR-IOV + RDMA + NUMA pinning 達成更極致的版本。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計延遲敏感型服務的容量地圖 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想搞清楚哪些服務該水平擴容、哪些不該 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>想做 latency budget 反推 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>對照不同形狀的負載 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a>（可預期極端峰值）/ <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a>（事件型不可預期峰值）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Launches an Ultra-Low-Latency Cryptocurrency Exchange on AWS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/coinbase-migration-case-study/">Coinbase Scales 50% Faster, Cuts Costs 62% with AWS</a></li>
<li><a href="https://aws.amazon.com/video/watch/a413043e5cb/">Ultra-Low-Latency Crypto Exchange on AWS（video）</a></li>
</ul>
]]></content:encoded></item><item><title>AWS：Control Plane 事故的責任邊界與通訊節奏樣式（2023）</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2023-control-plane-accountability-and-communication-pattern/</guid><description>&lt;p>這篇的核心責任是補齊「控制面事故如何說清楚責任邊界」。和 2017、2021 兩篇相比，這裡重點在事故治理樣式、單一技術細節是次要的：怎麼分辨控制面與資料面、怎麼維持對外更新節奏、怎麼保留決策脈絡。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>當控制面退化時，最容易出現三種混亂：第一，內部把多個症狀拆成獨立事件；第二，對外更新把控制面和資料面混在一起；第三，決策紀錄只留結論，沒有留下假設與回退條件。這三種混亂會直接拉長復原時間。&lt;/p>
&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>多服務管理 API 同步抖動&lt;/td>
 &lt;td>shared control plane 可能異常&lt;/td>
 &lt;td>先建立單一 incident thread&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料讀寫可用但控制操作失真&lt;/td>
 &lt;td>control/data plane 分離已發生&lt;/td>
 &lt;td>對外更新分兩條狀態敘述&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>更新頻率不穩、描述反覆修正&lt;/td>
 &lt;td>evidence pipeline 不穩定&lt;/td>
 &lt;td>固定更新 cadence 與欄位結構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回退有效但後續仍有殘留警訊&lt;/td>
 &lt;td>依賴鏈條尚未收斂&lt;/td>
 &lt;td>增加 dependency-level 驗證步驟&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故治理路徑樣式">事故治理路徑（樣式）&lt;/h2>
&lt;ol>
&lt;li>啟動單一事件線，避免按產品拆散。&lt;/li>
&lt;li>明確標註控制面與資料面狀態，分開追蹤。&lt;/li>
&lt;li>固定對外 cadence（例如每 30 分鐘）更新「已知 / 未知 / 下一步」。&lt;/li>
&lt;li>在 decision log 記錄假設、證據、回退條件與 owner。&lt;/li>
&lt;li>收斂後把通訊節奏與責任邊界回寫 runbook 與 evidence package。&lt;/li>
&lt;/ol>
&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>Incident decision log&lt;/td>
 &lt;td>事中假設與回退條件缺少結構化&lt;/td>
 &lt;td>強制套用 [8.19] 欄位（假設/證據/條件/責任）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Customer impact assessment&lt;/td>
 &lt;td>對外影響描述粒度不一致&lt;/td>
 &lt;td>在 [8.20] 補 control/data plane 影響分欄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Communication cadence&lt;/td>
 &lt;td>更新節奏受資訊不完整影響&lt;/td>
 &lt;td>在 [8.4] 固定 cadence 與狀態模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence package&lt;/td>
 &lt;td>事後很難回推當時判斷基礎&lt;/td>
 &lt;td>在 [4.20] 補控制面健康、依賴鏈與更新記錄欄位&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>事故決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>客戶影響評估： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment&lt;/a>&lt;/li>
&lt;li>事故通訊： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication&lt;/a>&lt;/li>
&lt;li>觀測證據包： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://health.aws.amazon.com/health/status">AWS Service Health Dashboard&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/message/">AWS post-event summaries&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這篇的核心責任是補齊「控制面事故如何說清楚責任邊界」。和 2017、2021 兩篇相比，這裡重點在事故治理樣式、單一技術細節是次要的：怎麼分辨控制面與資料面、怎麼維持對外更新節奏、怎麼保留決策脈絡。</p>
<h2 id="問題場景">問題場景</h2>
<p>當控制面退化時，最容易出現三種混亂：第一，內部把多個症狀拆成獨立事件；第二，對外更新把控制面和資料面混在一起；第三，決策紀錄只留結論，沒有留下假設與回退條件。這三種混亂會直接拉長復原時間。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>代表意義</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多服務管理 API 同步抖動</td>
          <td>shared control plane 可能異常</td>
          <td>先建立單一 incident thread</td>
      </tr>
      <tr>
          <td>資料讀寫可用但控制操作失真</td>
          <td>control/data plane 分離已發生</td>
          <td>對外更新分兩條狀態敘述</td>
      </tr>
      <tr>
          <td>更新頻率不穩、描述反覆修正</td>
          <td>evidence pipeline 不穩定</td>
          <td>固定更新 cadence 與欄位結構</td>
      </tr>
      <tr>
          <td>回退有效但後續仍有殘留警訊</td>
          <td>依賴鏈條尚未收斂</td>
          <td>增加 dependency-level 驗證步驟</td>
      </tr>
  </tbody>
</table>
<h2 id="事故治理路徑樣式">事故治理路徑（樣式）</h2>
<ol>
<li>啟動單一事件線，避免按產品拆散。</li>
<li>明確標註控制面與資料面狀態，分開追蹤。</li>
<li>固定對外 cadence（例如每 30 分鐘）更新「已知 / 未知 / 下一步」。</li>
<li>在 decision log 記錄假設、證據、回退條件與 owner。</li>
<li>收斂後把通訊節奏與責任邊界回寫 runbook 與 evidence package。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>暴露缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Incident decision log</td>
          <td>事中假設與回退條件缺少結構化</td>
          <td>強制套用 [8.19] 欄位（假設/證據/條件/責任）</td>
      </tr>
      <tr>
          <td>Customer impact assessment</td>
          <td>對外影響描述粒度不一致</td>
          <td>在 [8.20] 補 control/data plane 影響分欄</td>
      </tr>
      <tr>
          <td>Communication cadence</td>
          <td>更新節奏受資訊不完整影響</td>
          <td>在 [8.4] 固定 cadence 與狀態模板</td>
      </tr>
      <tr>
          <td>Evidence package</td>
          <td>事後很難回推當時判斷基礎</td>
          <td>在 [4.20] 補控制面健康、依賴鏈與更新記錄欄位</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>事故決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>客戶影響評估： <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment</a></li>
<li>事故通訊： <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 Incident Communication</a></li>
<li>觀測證據包： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://health.aws.amazon.com/health/status">AWS Service Health Dashboard</a></li>
<li><a href="https://aws.amazon.com/message/">AWS post-event summaries</a></li>
</ul>
]]></content:encoded></item><item><title>2.C3 Shopify：快取序列化格式遷移</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-cache-serialization-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-cache-serialization-migration/</guid><description>&lt;p>這個案例的核心責任是說明快取轉換常見的格式遷移如何安全落地。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Shopify 在快取編碼轉換過程使用雙軌策略，先允許新舊格式共存，再逐步收斂。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>快取格式轉換本質上是相容性遷移。若一次切換，回退與資料可讀性風險會放大。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>新格式可編碼就先寫新格式。&lt;/li>
&lt;li>編碼失敗回落舊格式，保留服務可用性。&lt;/li>
&lt;li>維持一段雙軌期，觀測命中率與錯誤率再收斂。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://shopify.engineering/caching-without-marshal-part-two-messagepack">Caching Without Marshal Part 2&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取轉換常見的格式遷移如何安全落地。</p>
<h2 id="觀察">觀察</h2>
<p>Shopify 在快取編碼轉換過程使用雙軌策略，先允許新舊格式共存，再逐步收斂。</p>
<h2 id="判讀">判讀</h2>
<p>快取格式轉換本質上是相容性遷移。若一次切換，回退與資料可讀性風險會放大。</p>
<h2 id="策略">策略</h2>
<ol>
<li>新格式可編碼就先寫新格式。</li>
<li>編碼失敗回落舊格式，保留服務可用性。</li>
<li>維持一段雙軌期，觀測命中率與錯誤率再收斂。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside</a> 與 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/caching-without-marshal-part-two-messagepack">Caching Without Marshal Part 2</a></li>
</ul>
]]></content:encoded></item><item><title>3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/</guid><description>&lt;p>這個案例的核心責任是說明 queue 系統的轉換也包含 metadata 治理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LinkedIn 以 TopicGC 清理未使用 topic，降低 Kafka metadata 壓力並改善 produce/consume 效能。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 queue 規模擴大，僅靠容量擴充不夠，topic 生命週期與治理自動化會成為可靠性關鍵。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>定義 topic 活躍判準與回收條件。&lt;/li>
&lt;li>自動化清理流程並保留稽核紀錄。&lt;/li>
&lt;li>監控清理前後的性能與穩定性指標。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2022/topicgc_how-linkedin-cleans-up-unused-metadata-for-its-kafka-clu">TopicGC at LinkedIn&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 queue 系統的轉換也包含 metadata 治理。</p>
<h2 id="觀察">觀察</h2>
<p>LinkedIn 以 TopicGC 清理未使用 topic，降低 Kafka metadata 壓力並改善 produce/consume 效能。</p>
<h2 id="判讀">判讀</h2>
<p>當 queue 規模擴大，僅靠容量擴充不夠，topic 生命週期與治理自動化會成為可靠性關鍵。</p>
<h2 id="策略">策略</h2>
<ol>
<li>定義 topic 活躍判準與回收條件。</li>
<li>自動化清理流程並保留稽核紀錄。</li>
<li>監控清理前後的性能與穩定性指標。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a> 與 <a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2022/topicgc_how-linkedin-cleans-up-unused-metadata-for-its-kafka-clu">TopicGC at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>5.C3 Orbitera：遷移到 Managed Kubernetes</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/orbitera-managed-kubernetes-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/orbitera-managed-kubernetes-migration/</guid><description>&lt;p>這個案例的核心責任是說明平台遷移的關鍵在服務連續性與能力重建，單次技術替換只是其中一步。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Orbitera 原本在 AWS 上以 EC2 為基礎運行 monolithic 架構，使用 EC2 + S3 + RDS + RedShift 組合。被 Google Cloud 收購後，在產品持續運作的前提下遷移到 Google Kubernetes Engine（GKE），同時從 monolith 重構為 microservices 架構。&lt;/p>
&lt;p>遷移後的架構運行在 multi-zone 配置下，每個 zone 維持 3 個 replica，確保單一 zone 故障時服務不中斷。整合 Cloud SQL（取代 RDS）、Google 的 load balancer、Stackdriver（觀測）。遷移完成後取得的操作能力包含 on-demand scaling、快速部署到新 region/zone、以及快速 rollback 失敗的 build。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>跨平台遷移本質是能力遷移：部署、觀測、恢復與團隊流程都需要同步重建。Orbitera 的遷移同時改變了兩個維度——平台（AWS → GCP）和架構（monolith → microservices）。雙維度同時改變放大了遷移風險，但也讓團隊避免了「先遷平台再拆架構」的兩階段成本。&lt;/p>
&lt;p>這個案例揭露的隱性工作量在「能力對等重建」。原本在 AWS 上已經建好的觀測（CloudWatch → Stackdriver）、資料庫操作（RDS → Cloud SQL）、load balancing 都要在新平台上重新建立並驗證。這些能力不會隨著 workload 遷移自動出現——需要明確的 checklist 和驗證流程。&lt;/p>
&lt;p>monolith → microservices 的架構重構改變了 runtime 的基本假設。Monolith 的 readiness 是單一進程啟動完成；microservices 的 readiness 涉及多個服務之間的依賴就緒。&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract&lt;/a> 的 readiness 設計取捨在這類重構後需要重新定義——哪些是必要依賴、哪些是可降級依賴，從 monolith 時代的「全部在同一個進程」變成需要顯式判斷。&lt;/p>
&lt;p>Multi-zone HA（3 replicas/zone）是遷移後 managed 平台提供的基線能力。在 self-managed 環境下實現相同程度的跨 zone 冗餘需要大量手動配置（zone-aware scheduling、cross-zone load balancing）；managed 平台把這些收進平台層，團隊精力從「維持 HA 運作」轉向「定義 HA 目標」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>先驗證新平台的最小可行服務&lt;/strong>：選擇一個依賴少、風險低的服務在 GKE 上完成完整 deployment cycle（build → deploy → observe → rollback），驗證 CI/CD pipeline、觀測整合、rollback 路徑都可運作。&lt;/li>
&lt;li>&lt;strong>建立能力對等 checklist&lt;/strong>：列出舊平台已有的操作能力（觀測、告警、backup、secret 管理、log 收集），逐一確認新平台有對應方案且經過驗證。未對等的能力是遷移的 blocking 條件。&lt;/li>
&lt;li>&lt;strong>逐步搬遷核心工作負載&lt;/strong>：按依賴關係排序遷移批次，保留舊平台的回切路徑。每批遷移後在新平台上跑 load test 驗證容量與恢復能力。&lt;/li>
&lt;li>&lt;strong>把平台能力納入日常治理節奏&lt;/strong>：遷移完成不是終點——GKE 版本升級、node pool 更新、Cloud SQL 維護窗口都要進入團隊的日常操作流程，避免遷移後進入「只部署不維護」的狀態。&lt;/li>
&lt;/ol>
&lt;h2 id="可回寫的章節段落">可回寫的章節段落&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 Container Runtime — 遷移期的 Runtime 穩定性&lt;/a>：monolith → microservices 改變 image 建置策略與啟動行為&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract — 遷移期的 Lifecycle 重新驗證&lt;/a>：readiness 條件在架構重構後需重新定義&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR/Rollback Rehearsal&lt;/a>：遷移後的回退路徑驗證&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/gcp/why-we-migrated-orbitera-to-managed-kubernetes-on-google-cloud-platform/">Why we migrated Orbitera to managed Kubernetes on Google Cloud Platform&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明平台遷移的關鍵在服務連續性與能力重建，單次技術替換只是其中一步。</p>
<h2 id="觀察">觀察</h2>
<p>Orbitera 原本在 AWS 上以 EC2 為基礎運行 monolithic 架構，使用 EC2 + S3 + RDS + RedShift 組合。被 Google Cloud 收購後，在產品持續運作的前提下遷移到 Google Kubernetes Engine（GKE），同時從 monolith 重構為 microservices 架構。</p>
<p>遷移後的架構運行在 multi-zone 配置下，每個 zone 維持 3 個 replica，確保單一 zone 故障時服務不中斷。整合 Cloud SQL（取代 RDS）、Google 的 load balancer、Stackdriver（觀測）。遷移完成後取得的操作能力包含 on-demand scaling、快速部署到新 region/zone、以及快速 rollback 失敗的 build。</p>
<h2 id="判讀">判讀</h2>
<p>跨平台遷移本質是能力遷移：部署、觀測、恢復與團隊流程都需要同步重建。Orbitera 的遷移同時改變了兩個維度——平台（AWS → GCP）和架構（monolith → microservices）。雙維度同時改變放大了遷移風險，但也讓團隊避免了「先遷平台再拆架構」的兩階段成本。</p>
<p>這個案例揭露的隱性工作量在「能力對等重建」。原本在 AWS 上已經建好的觀測（CloudWatch → Stackdriver）、資料庫操作（RDS → Cloud SQL）、load balancing 都要在新平台上重新建立並驗證。這些能力不會隨著 workload 遷移自動出現——需要明確的 checklist 和驗證流程。</p>
<p>monolith → microservices 的架構重構改變了 runtime 的基本假設。Monolith 的 readiness 是單一進程啟動完成；microservices 的 readiness 涉及多個服務之間的依賴就緒。<a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract</a> 的 readiness 設計取捨在這類重構後需要重新定義——哪些是必要依賴、哪些是可降級依賴，從 monolith 時代的「全部在同一個進程」變成需要顯式判斷。</p>
<p>Multi-zone HA（3 replicas/zone）是遷移後 managed 平台提供的基線能力。在 self-managed 環境下實現相同程度的跨 zone 冗餘需要大量手動配置（zone-aware scheduling、cross-zone load balancing）；managed 平台把這些收進平台層，團隊精力從「維持 HA 運作」轉向「定義 HA 目標」。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>先驗證新平台的最小可行服務</strong>：選擇一個依賴少、風險低的服務在 GKE 上完成完整 deployment cycle（build → deploy → observe → rollback），驗證 CI/CD pipeline、觀測整合、rollback 路徑都可運作。</li>
<li><strong>建立能力對等 checklist</strong>：列出舊平台已有的操作能力（觀測、告警、backup、secret 管理、log 收集），逐一確認新平台有對應方案且經過驗證。未對等的能力是遷移的 blocking 條件。</li>
<li><strong>逐步搬遷核心工作負載</strong>：按依賴關係排序遷移批次，保留舊平台的回切路徑。每批遷移後在新平台上跑 load test 驗證容量與恢復能力。</li>
<li><strong>把平台能力納入日常治理節奏</strong>：遷移完成不是終點——GKE 版本升級、node pool 更新、Cloud SQL 維護窗口都要進入團隊的日常操作流程，避免遷移後進入「只部署不維護」的狀態。</li>
</ol>
<h2 id="可回寫的章節段落">可回寫的章節段落</h2>
<ul>
<li><a href="/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 Container Runtime — 遷移期的 Runtime 穩定性</a>：monolith → microservices 改變 image 建置策略與啟動行為</li>
<li><a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract — 遷移期的 Lifecycle 重新驗證</a>：readiness 條件在架構重構後需重新定義</li>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR/Rollback Rehearsal</a>：遷移後的回退路徑驗證</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/gcp/why-we-migrated-orbitera-to-managed-kubernetes-on-google-cloud-platform/">Why we migrated Orbitera to managed Kubernetes on Google Cloud Platform</a></li>
</ul>
]]></content:encoded></item><item><title>7.C3 Azure AD：2021 Identity Control-plane 事件</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/azure-ad-identity-control-plane-2021/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/azure-ad-identity-control-plane-2021/</guid><description>&lt;p>這個案例的核心責任是說明身份服務控制面故障會外溢成大範圍服務故障。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Azure AD 控制面事件導致多個依賴身份驗證的服務受影響，事故處理需要同時兼顧身份恢復與服務降級策略。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當身份系統是共同依賴，問題會跨產品線傳播，必須把身份恢復路徑與業務優先序綁定管理。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>建立身份控制面的降級與隔離策略。&lt;/li>
&lt;li>讓關鍵服務支援有限模式運行。&lt;/li>
&lt;li>在 incident command 中獨立處理 identity workstream。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 identity and access boundary&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.8 security vs operational incident&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.microsoft.com/en-us/security/blog/2021/03/17/azure-active-directory-resilience-lessons-from-the-march-15-2021-incident/">Azure AD 2021 incident&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明身份服務控制面故障會外溢成大範圍服務故障。</p>
<h2 id="觀察">觀察</h2>
<p>Azure AD 控制面事件導致多個依賴身份驗證的服務受影響，事故處理需要同時兼顧身份恢復與服務降級策略。</p>
<h2 id="判讀">判讀</h2>
<p>當身份系統是共同依賴，問題會跨產品線傳播，必須把身份恢復路徑與業務優先序綁定管理。</p>
<h2 id="策略">策略</h2>
<ol>
<li>建立身份控制面的降級與隔離策略。</li>
<li>讓關鍵服務支援有限模式運行。</li>
<li>在 incident command 中獨立處理 identity workstream。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 identity and access boundary</a> 與 <a href="/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.8 security vs operational incident</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en-us/security/blog/2021/03/17/azure-active-directory-resilience-lessons-from-the-march-15-2021-incident/">Azure AD 2021 incident</a></li>
</ul>
]]></content:encoded></item><item><title>Cloudflare 2026 BYOIP BGP Withdrawal</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/</guid><description>&lt;p>2026 年 Cloudflare BYOIP / BGP 事故的核心教訓是：控制面資料一旦同時承擔 customer configuration 與 operational state，錯誤清理流程會直接變成全網路由變更。這類事故的第一責任是停止錯誤狀態傳播，再把 desired state 與 actual state 拆開恢復。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cloudflare 在 2026-02-20 17:48 UTC 發生 BYOIP 相關 outage。部分使用 Bring Your Own IP（BYOIP）的客戶，其 IP prefixes 被 Cloudflare 經由 BGP 非預期撤告，導致相關服務從 Internet 無法到達。官方回顧指出，事故總時長為 6 小時 7 分鐘；在 4,306 個 BYOIP prefixes 中，約 1,100 個 prefixes 曾被撤告，約佔 BYOIP prefixes 的 25%。&lt;/p>
&lt;p>事故起因是 Cloudflare 在 Addressing API / BYOIP pipeline 中引入的自動化清理流程，與外部攻擊無關。該流程原本要移除 pending deletion 的 prefixes，但 API query 的 &lt;code>pending_delete&lt;/code> 參數沒有值，server 端將它解讀成一般查詢，回傳所有 BYOIP prefixes。下游流程接著把回傳結果當成待刪除集合，開始撤告 prefixes 與移除相關 service bindings。&lt;/p>
&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>BYOIP prefixes 數量快速下降&lt;/td>
 &lt;td>BGP advertisement 正在被控制面錯誤改寫&lt;/td>
 &lt;td>立即停止最新 Addressing API / cleanup 任務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶服務從 Internet 無法連線&lt;/td>
 &lt;td>prefix withdrawal 已影響資料面可達性&lt;/td>
 &lt;td>優先恢復 prefix advertisement，而非只查應用層錯誤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部分客戶可自行 re-advertise&lt;/td>
 &lt;td>部分狀態只被撤告，binding 尚未被刪除&lt;/td>
 &lt;td>對外提供 dashboard workaround，降低待處理影響面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部分客戶無法自助恢復&lt;/td>
 &lt;td>service bindings 或 edge 設定也被移除&lt;/td>
 &lt;td>需要工程團隊做資料恢復與 global configuration rollout&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>恢復分成多批完成&lt;/td>
 &lt;td>受影響 prefixes 處於不同損壞狀態&lt;/td>
 &lt;td>decision log 要分別記錄「可自助」「需手動」「需全域 rollout」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>Addressing API 相關程式碼在 2026-02-05 合併，並於 2026-02-20 部署。&lt;/li>
&lt;li>cleanup sub-task 查詢 &lt;code>/v1/prefixes?pending_delete&lt;/code>，但 &lt;code>pending_delete&lt;/code> 沒有值。&lt;/li>
&lt;li>API server 沒有進入 pending deletion 分支，而是回傳所有 BYOIP prefixes。&lt;/li>
&lt;li>cleanup sub-task 將回傳的 prefixes 解讀成待移除集合，開始撤告 prefixes 與刪除 dependent objects。&lt;/li>
&lt;li>Cloudflare 在觀察到 1.1.1.1 相關失敗後回退變更並終止 broken sub-process。&lt;/li>
&lt;li>多數 prefixes 透過 re-advertise 或 restore 流程恢復，剩餘約 300 個 prefixes 需要工程師手動恢復 service bindings 與 edge 設定。&lt;/li>
&lt;/ol>
&lt;p>這條路徑顯示：BGP withdrawal 是結果，真正的事故起點是控制面資料查詢語意不明確，以及 operational workflow 對查詢結果缺少大範圍變更 circuit breaker。&lt;/p>
&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>API schema&lt;/td>
 &lt;td>boolean-like query 參數語意不明確&lt;/td>
 &lt;td>將狀態查詢參數標準化，錯誤或空值直接拒絕，不進入危險預設路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Desired / actual state 分離&lt;/td>
 &lt;td>customer configuration 與 operational action 混在同一資料面&lt;/td>
 &lt;td>引入 snapshot / staged deployment，讓壞資料可快速回到 known-good state&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>大範圍 withdrawal circuit breaker&lt;/td>
 &lt;td>cleanup 任務可一次影響大量 prefixes&lt;/td>
 &lt;td>對 prefix withdrawal / deletion 設速率、數量與健康訊號閘門&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Staging 與 mock data&lt;/td>
 &lt;td>測試資料未覆蓋 task-runner 自主操作情境&lt;/td>
 &lt;td>補 production-like state mutation 測試，而不只測 customer journey&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident intake&lt;/td>
 &lt;td>1.1.1.1 異常成為早期觀察點&lt;/td>
 &lt;td>將共享基礎服務異常納入控制面事故快速升級條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence write-back&lt;/td>
 &lt;td>恢復分成 dashboard 自助、資料修復、global rollout 多條路&lt;/td>
 &lt;td>回寫 decision log 與 evidence package，保留每種狀態的恢復判準&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>控制面資料品質： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality&lt;/a>&lt;/li>
&lt;li>規則推送安全閘門： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a>&lt;/li>
&lt;li>變更安全邊界： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary&lt;/a>&lt;/li>
&lt;li>驗證證據交接： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff&lt;/a>&lt;/li>
&lt;li>事故決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>證據回寫流程： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/cloudflare-outage-february-20-2026/">Cloudflare outage on February 20, 2026&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>2026 年 Cloudflare BYOIP / BGP 事故的核心教訓是：控制面資料一旦同時承擔 customer configuration 與 operational state，錯誤清理流程會直接變成全網路由變更。這類事故的第一責任是停止錯誤狀態傳播，再把 desired state 與 actual state 拆開恢復。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Cloudflare 在 2026-02-20 17:48 UTC 發生 BYOIP 相關 outage。部分使用 Bring Your Own IP（BYOIP）的客戶，其 IP prefixes 被 Cloudflare 經由 BGP 非預期撤告，導致相關服務從 Internet 無法到達。官方回顧指出，事故總時長為 6 小時 7 分鐘；在 4,306 個 BYOIP prefixes 中，約 1,100 個 prefixes 曾被撤告，約佔 BYOIP prefixes 的 25%。</p>
<p>事故起因是 Cloudflare 在 Addressing API / BYOIP pipeline 中引入的自動化清理流程，與外部攻擊無關。該流程原本要移除 pending deletion 的 prefixes，但 API query 的 <code>pending_delete</code> 參數沒有值，server 端將它解讀成一般查詢，回傳所有 BYOIP prefixes。下游流程接著把回傳結果當成待刪除集合，開始撤告 prefixes 與移除相關 service bindings。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>事故中代表什麼</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BYOIP prefixes 數量快速下降</td>
          <td>BGP advertisement 正在被控制面錯誤改寫</td>
          <td>立即停止最新 Addressing API / cleanup 任務</td>
      </tr>
      <tr>
          <td>客戶服務從 Internet 無法連線</td>
          <td>prefix withdrawal 已影響資料面可達性</td>
          <td>優先恢復 prefix advertisement，而非只查應用層錯誤</td>
      </tr>
      <tr>
          <td>部分客戶可自行 re-advertise</td>
          <td>部分狀態只被撤告，binding 尚未被刪除</td>
          <td>對外提供 dashboard workaround，降低待處理影響面</td>
      </tr>
      <tr>
          <td>部分客戶無法自助恢復</td>
          <td>service bindings 或 edge 設定也被移除</td>
          <td>需要工程團隊做資料恢復與 global configuration rollout</td>
      </tr>
      <tr>
          <td>恢復分成多批完成</td>
          <td>受影響 prefixes 處於不同損壞狀態</td>
          <td>decision log 要分別記錄「可自助」「需手動」「需全域 rollout」</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>Addressing API 相關程式碼在 2026-02-05 合併，並於 2026-02-20 部署。</li>
<li>cleanup sub-task 查詢 <code>/v1/prefixes?pending_delete</code>，但 <code>pending_delete</code> 沒有值。</li>
<li>API server 沒有進入 pending deletion 分支，而是回傳所有 BYOIP prefixes。</li>
<li>cleanup sub-task 將回傳的 prefixes 解讀成待移除集合，開始撤告 prefixes 與刪除 dependent objects。</li>
<li>Cloudflare 在觀察到 1.1.1.1 相關失敗後回退變更並終止 broken sub-process。</li>
<li>多數 prefixes 透過 re-advertise 或 restore 流程恢復，剩餘約 300 個 prefixes 需要工程師手動恢復 service bindings 與 edge 設定。</li>
</ol>
<p>這條路徑顯示：BGP withdrawal 是結果，真正的事故起點是控制面資料查詢語意不明確，以及 operational workflow 對查詢結果缺少大範圍變更 circuit breaker。</p>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>這次事故暴露的缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>API schema</td>
          <td>boolean-like query 參數語意不明確</td>
          <td>將狀態查詢參數標準化，錯誤或空值直接拒絕，不進入危險預設路徑</td>
      </tr>
      <tr>
          <td>Desired / actual state 分離</td>
          <td>customer configuration 與 operational action 混在同一資料面</td>
          <td>引入 snapshot / staged deployment，讓壞資料可快速回到 known-good state</td>
      </tr>
      <tr>
          <td>大範圍 withdrawal circuit breaker</td>
          <td>cleanup 任務可一次影響大量 prefixes</td>
          <td>對 prefix withdrawal / deletion 設速率、數量與健康訊號閘門</td>
      </tr>
      <tr>
          <td>Staging 與 mock data</td>
          <td>測試資料未覆蓋 task-runner 自主操作情境</td>
          <td>補 production-like state mutation 測試，而不只測 customer journey</td>
      </tr>
      <tr>
          <td>Incident intake</td>
          <td>1.1.1.1 異常成為早期觀察點</td>
          <td>將共享基礎服務異常納入控制面事故快速升級條件</td>
      </tr>
      <tr>
          <td>Evidence write-back</td>
          <td>恢復分成 dashboard 自助、資料修復、global rollout 多條路</td>
          <td>回寫 decision log 與 evidence package，保留每種狀態的恢復判準</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>控制面資料品質： <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a></li>
<li>規則推送安全閘門： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a></li>
<li>變更安全邊界： <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary</a></li>
<li>驗證證據交接： <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff</a></li>
<li>事故決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>證據回寫流程： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/cloudflare-outage-february-20-2026/">Cloudflare outage on February 20, 2026</a></li>
</ul>
]]></content:encoded></item><item><title>Healthcare：存取可追溯性與保留邊界</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/</guid><description>&lt;p>本案例的核心責任是讓資料主權場景下的觀測仍可追溯。Healthcare 系統常同時面臨最小存取原則、資料留存規範與跨團隊協作需求。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一個遠距醫療平台，服務多家醫療機構（multi-tenant），處理病歷查閱、處方開立、檢驗報告與預約排程。平台受 HIPAA 跟當地個資法規範，稽核單位要求能回答「哪個使用者在什麼時間查看了哪個病患的哪份紀錄」。&lt;/p>
&lt;p>初期系統的存取紀錄散落在各服務的 application log 中 — 病歷服務記了一筆 &lt;code>GET /patient/123/records&lt;/code>，處方服務記了一筆 &lt;code>POST /prescription&lt;/code>，但兩者沒有共同的 correlation key。稽核問「護理師 A 在 3 月 15 日存取了哪些病歷」時，工程師需要在四個服務各自 grep，再用 timestamp 近似對齊，整個流程耗時半天且結果不可靠。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="存取-log-與-application-log-混合">存取 log 與 application log 混合&lt;/h3>
&lt;p>存取紀錄（誰看了什麼）跟 operational log（request timing、error、retry）寫在同一個 pipeline。Application log 的 retention 設定 30 天（除錯夠用），但法規要求存取紀錄保留 6 年。等到稽核來查詢時，超過 30 天的存取紀錄已經被刪。&lt;/p>
&lt;h3 id="跨服務存取鏈斷裂">跨服務存取鏈斷裂&lt;/h3>
&lt;p>一次病歷查閱可能經過 API gateway → auth service → patient service → record service → audit service 五個服務。每個服務各自記 log，但沒有統一的 access event correlation。Auth service 知道「誰」，patient service 知道「看了哪個病患」，record service 知道「看了哪份紀錄」— 三段資訊散落在三個服務的 log 中，無法自動關聯。&lt;/p>
&lt;h3 id="multi-tenant-retention-差異">Multi-tenant retention 差異&lt;/h3>
&lt;p>不同醫療機構受不同法規管轄 — 機構 A 在美國需要 HIPAA 6 年 retention，機構 B 在歐盟需要 GDPR 的「目的限縮」原則（保留期限隨用途而定），機構 C 在台灣需要醫療法規定的 7 年。統一 retention policy 要嘛過度保留（增加成本與 PII 暴露面），要嘛保留不足（法規風險）。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="data-access-audit-log-獨立-pipeline">Data access audit log 獨立 pipeline&lt;/h3>
&lt;p>把存取事件從 application log 分離出來。每當使用者查閱、修改或匯出 PHI（Protected Health Information）時，產生結構化 access event：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&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="nt">&amp;#34;event_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;phi_access&amp;#34;&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="nt">&amp;#34;actor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nurse-a@hospital-x.com&amp;#34;&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="nt">&amp;#34;patient_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;P-2048&amp;#34;&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="nt">&amp;#34;resource&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;medical_record/lab_result/2026-03-15&amp;#34;&lt;/span>&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 class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;view&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;trace_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;abc123&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;access_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;acc-789&amp;#34;&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="nt">&amp;#34;tenant&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;hospital-x&amp;#34;&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="nt">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2026-03-15T14:22:05Z&amp;#34;&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;/code>&lt;/pre>&lt;/div>&lt;p>Access event 寫入獨立的 immutable storage（append-only log），跟 application log 分開的 pipeline 與 retention。&lt;/p>
&lt;h3 id="cross-service-access-chain">Cross-service access chain&lt;/h3>
&lt;p>在 API gateway 入口產生 &lt;code>access_id&lt;/code>，跟 &lt;code>trace_id&lt;/code> 一起透過 context propagation 傳遞到所有下游服務。每個服務在產生 access event 時帶上這兩個 key。查詢時用 &lt;code>access_id&lt;/code> 就能撈出一次存取操作在所有服務的完整軌跡，不需要手動拼接。&lt;/p>
&lt;p>&lt;code>trace_id&lt;/code> 用於關聯 operational 訊號（latency、error），&lt;code>access_id&lt;/code> 用於關聯合規稽核。兩者可以相同也可以不同 — 關鍵是 access event 要同時帶兩個 key。&lt;/p>
&lt;h3 id="分層-retention-與-tenant-level-policy">分層 retention 與 tenant-level policy&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層級&lt;/th>
 &lt;th>儲存&lt;/th>
 &lt;th>Retention&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Hot&lt;/td>
 &lt;td>搜尋引擎（Elasticsearch / Cloud Logging）&lt;/td>
 &lt;td>90 天&lt;/td>
 &lt;td>即時查詢、事故調查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Warm&lt;/td>
 &lt;td>Object storage（壓縮）&lt;/td>
 &lt;td>2 年&lt;/td>
 &lt;td>定期稽核、合規查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cold&lt;/td>
 &lt;td>Archive storage（冰凍）&lt;/td>
 &lt;td>6-7 年（依 tenant 法規）&lt;/td>
 &lt;td>法規保留、法務調查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個 tenant 在平台建立時設定法規要求的 retention 期限。Pipeline 根據 tenant tag 自動把 access event 路由到對應的 retention tier。Tenant A 的紀錄到第 6 年自動歸檔到 cold，tenant B 在 GDPR 目的屆滿時觸發刪除審核。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是讓資料主權場景下的觀測仍可追溯。Healthcare 系統常同時面臨最小存取原則、資料留存規範與跨團隊協作需求。</p>
<h2 id="業務背景">業務背景</h2>
<p>一個遠距醫療平台，服務多家醫療機構（multi-tenant），處理病歷查閱、處方開立、檢驗報告與預約排程。平台受 HIPAA 跟當地個資法規範，稽核單位要求能回答「哪個使用者在什麼時間查看了哪個病患的哪份紀錄」。</p>
<p>初期系統的存取紀錄散落在各服務的 application log 中 — 病歷服務記了一筆 <code>GET /patient/123/records</code>，處方服務記了一筆 <code>POST /prescription</code>，但兩者沒有共同的 correlation key。稽核問「護理師 A 在 3 月 15 日存取了哪些病歷」時，工程師需要在四個服務各自 grep，再用 timestamp 近似對齊，整個流程耗時半天且結果不可靠。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="存取-log-與-application-log-混合">存取 log 與 application log 混合</h3>
<p>存取紀錄（誰看了什麼）跟 operational log（request timing、error、retry）寫在同一個 pipeline。Application log 的 retention 設定 30 天（除錯夠用），但法規要求存取紀錄保留 6 年。等到稽核來查詢時，超過 30 天的存取紀錄已經被刪。</p>
<h3 id="跨服務存取鏈斷裂">跨服務存取鏈斷裂</h3>
<p>一次病歷查閱可能經過 API gateway → auth service → patient service → record service → audit service 五個服務。每個服務各自記 log，但沒有統一的 access event correlation。Auth service 知道「誰」，patient service 知道「看了哪個病患」，record service 知道「看了哪份紀錄」— 三段資訊散落在三個服務的 log 中，無法自動關聯。</p>
<h3 id="multi-tenant-retention-差異">Multi-tenant retention 差異</h3>
<p>不同醫療機構受不同法規管轄 — 機構 A 在美國需要 HIPAA 6 年 retention，機構 B 在歐盟需要 GDPR 的「目的限縮」原則（保留期限隨用途而定），機構 C 在台灣需要醫療法規定的 7 年。統一 retention policy 要嘛過度保留（增加成本與 PII 暴露面），要嘛保留不足（法規風險）。</p>
<h2 id="解法">解法</h2>
<h3 id="data-access-audit-log-獨立-pipeline">Data access audit log 獨立 pipeline</h3>
<p>把存取事件從 application log 分離出來。每當使用者查閱、修改或匯出 PHI（Protected Health Information）時，產生結構化 access event：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nt">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;phi_access&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;actor&#34;</span><span class="p">:</span> <span class="s2">&#34;nurse-a@hospital-x.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nt">&#34;patient_id&#34;</span><span class="p">:</span> <span class="s2">&#34;P-2048&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nt">&#34;resource&#34;</span><span class="p">:</span> <span class="s2">&#34;medical_record/lab_result/2026-03-15&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;view&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="nt">&#34;trace_id&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nt">&#34;access_id&#34;</span><span class="p">:</span> <span class="s2">&#34;acc-789&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nt">&#34;tenant&#34;</span><span class="p">:</span> <span class="s2">&#34;hospital-x&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nt">&#34;timestamp&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-03-15T14:22:05Z&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Access event 寫入獨立的 immutable storage（append-only log），跟 application log 分開的 pipeline 與 retention。</p>
<h3 id="cross-service-access-chain">Cross-service access chain</h3>
<p>在 API gateway 入口產生 <code>access_id</code>，跟 <code>trace_id</code> 一起透過 context propagation 傳遞到所有下游服務。每個服務在產生 access event 時帶上這兩個 key。查詢時用 <code>access_id</code> 就能撈出一次存取操作在所有服務的完整軌跡，不需要手動拼接。</p>
<p><code>trace_id</code> 用於關聯 operational 訊號（latency、error），<code>access_id</code> 用於關聯合規稽核。兩者可以相同也可以不同 — 關鍵是 access event 要同時帶兩個 key。</p>
<h3 id="分層-retention-與-tenant-level-policy">分層 retention 與 tenant-level policy</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>儲存</th>
          <th>Retention</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hot</td>
          <td>搜尋引擎（Elasticsearch / Cloud Logging）</td>
          <td>90 天</td>
          <td>即時查詢、事故調查</td>
      </tr>
      <tr>
          <td>Warm</td>
          <td>Object storage（壓縮）</td>
          <td>2 年</td>
          <td>定期稽核、合規查詢</td>
      </tr>
      <tr>
          <td>Cold</td>
          <td>Archive storage（冰凍）</td>
          <td>6-7 年（依 tenant 法規）</td>
          <td>法規保留、法務調查</td>
      </tr>
  </tbody>
</table>
<p>每個 tenant 在平台建立時設定法規要求的 retention 期限。Pipeline 根據 tenant tag 自動把 access event 路由到對應的 retention tier。Tenant A 的紀錄到第 6 年自動歸檔到 cold，tenant B 在 GDPR 目的屆滿時觸發刪除審核。</p>
<h3 id="存取-log-中的-pii-處理">存取 log 中的 PII 處理</h3>
<p>Access event 本身包含 <code>patient_id</code> 跟 <code>actor</code>，這些在存取紀錄中是必要資訊（「誰看了什麼」需要這兩個欄位）。處理方式是存取控制而非遮罩 — access event storage 的讀取權限限縮到 compliance team 跟 audit 角色，engineering team 的一般查詢權限無法看到這些欄位。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>統一 retention</th>
          <th>分層 + tenant-level</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實作複雜度</td>
          <td>低</td>
          <td>高（routing 邏輯、多層 storage）</td>
      </tr>
      <tr>
          <td>儲存成本</td>
          <td>高（全部留最長）</td>
          <td>可控（各層各自成本）</td>
      </tr>
      <tr>
          <td>合規精確度</td>
          <td>低（過度保留或保留不足）</td>
          <td>高（對齊各 tenant 法規要求）</td>
      </tr>
      <tr>
          <td>刪除能力</td>
          <td>無法按 tenant 刪</td>
          <td>可（GDPR right to erasure）</td>
      </tr>
      <tr>
          <td>查詢效率</td>
          <td>全量搜尋</td>
          <td>Hot tier 秒級、Cold tier 分鐘到小時級</td>
      </tr>
  </tbody>
</table>
<p>分層架構的最大風險是跨層查詢的延遲 — 稽核要求「給我 3 年前的存取紀錄」時，cold tier 的解凍時間可能是小時級。解法是在稽核週期前預先解凍相關 tenant 的 cold archive 到 warm tier。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a>：audit log 分離與 PII 治理。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：access log pipeline 的 ownership 與 review cadence。</li>
<li><a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a>：timestamp integrity 跟跨服務時序校正。</li>
<li><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3 Tracing Context</a>：access_id 跟 trace_id 的 propagation 設計。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>稽核問「使用者 X 在某段時間存取了什麼」，回答需要超過數小時的手動拼接</li>
<li>存取紀錄的 retention 跟法規要求不一致，但沒人確切量化差距</li>
<li>Multi-tenant 環境中所有 tenant 共用同一個 retention policy，無法按法規區分</li>
<li>跨服務的存取事件無法自動關聯，需要靠 timestamp 近似比對</li>
<li>PHI 相關的 log 跟一般 application log 存在同一個 storage，存取控制無法區隔</li>
</ul>
]]></content:encoded></item><item><title>Healthcare：資料主權與回復順序選型</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cases/healthcare-data-sovereignty-and-recovery/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cases/healthcare-data-sovereignty-and-recovery/</guid><description>&lt;p>這個案例的核心責任是讓資料主權與可用性同時被治理。Healthcare 場景常同時面臨資料區域限制、最小存取原則與緊急回復需求。&lt;/p>
&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>cross-region data movement&lt;/td>
 &lt;td>是否違反主權邊界&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/security-data-protection-requirements/" data-link-title="0.8 資安與資料保護需求" data-link-desc="從權限分級、伺服器防護、資料遮罩、傳輸保護與稽核設計安全邊界">0.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>access audit completeness&lt;/td>
 &lt;td>存取證據是否可追溯&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>recovery ordering conflict&lt;/td>
 &lt;td>回復步驟是否與合規衝突&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="風險與邊界">風險與邊界&lt;/h2>
&lt;p>將合規需求與 DR 流程分開設計，容易在事故時出現互斥決策。較穩定做法是先定義可恢復資料集合與不可跨境資料集合，再安排回復順序。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先補 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18&lt;/a> 的責任邊界，再在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a> 驗證回復流程。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是讓資料主權與可用性同時被治理。Healthcare 場景常同時面臨資料區域限制、最小存取原則與緊急回復需求。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>cross-region data movement</td>
          <td>是否違反主權邊界</td>
          <td><a href="/blog/backend/00-service-selection/security-data-protection-requirements/" data-link-title="0.8 資安與資料保護需求" data-link-desc="從權限分級、伺服器防護、資料遮罩、傳輸保護與稽核設計安全邊界">0.8</a></td>
      </tr>
      <tr>
          <td>access audit completeness</td>
          <td>存取證據是否可追溯</td>
          <td><a href="/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2</a></td>
      </tr>
      <tr>
          <td>recovery ordering conflict</td>
          <td>回復步驟是否與合規衝突</td>
          <td><a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a></td>
      </tr>
  </tbody>
</table>
<h2 id="風險與邊界">風險與邊界</h2>
<p>將合規需求與 DR 流程分開設計，容易在事故時出現互斥決策。較穩定做法是先定義可恢復資料集合與不可跨境資料集合，再安排回復順序。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先補 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a> 的責任邊界，再在 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a> 驗證回復流程。</p>
]]></content:encoded></item><item><title>Amazon</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/</guid><description>&lt;p>Amazon 是 cell-based architecture 與 shuffle sharding 的代表、AWS Builders&amp;rsquo; Library 是大規模分散式系統的工程實踐 SSoT。教學重點在「如何設計才能讓失效局部化」。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Cell-based Architecture：把服務切成獨立 cell、每個 cell 有完整 stack&lt;/li>
&lt;li>Shuffle Sharding：客戶請求映射到 cell 的隨機切分、讓單一壞客戶無法擊倒所有 cell&lt;/li>
&lt;li>Static Stability：control plane 失效時 data plane 仍能服務&lt;/li>
&lt;li>Constant Work Pattern：avoid scaling traffic in failure modes&lt;/li>
&lt;li>AWS Builders&amp;rsquo; Library：可重用 reliability patterns 的官方文件&lt;/li>
&lt;/ul>
&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>Cell-based Architecture&lt;/td>
 &lt;td>DynamoDB / Route 53 / S3 的 cell 劃分原則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shuffle Sharding&lt;/td>
 &lt;td>數學上的 blast radius 量化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Static Stability&lt;/td>
 &lt;td>control / data plane 分離的設計取捨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Workload Isolation&lt;/td>
 &lt;td>tenancy / region / availability zone 的隔離層級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build with constant work&lt;/td>
 &lt;td>為何 push-based 比 pull-based 在 failure 時更穩定&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1&lt;/a>&lt;/td>
 &lt;td>Shuffle Sharding 與 Cell 邊界&lt;/td>
 &lt;td>用局部隔離限制多租戶擴散，讓恢復可以分批收斂&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">A2&lt;/a>&lt;/td>
 &lt;td>Static Stability 與 Constant Work Pattern&lt;/td>
 &lt;td>控制面失效時資料面用快取與固定工作量維持服務&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Amazon 這個案例在講的是可靠性如何靠隔離來守住擴散邊界。讀者先看懂 cell-based architecture 與 shuffle sharding 的責任，再把它們當成控制 blast radius 的設計語言，而不是單純的 AWS 名詞。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當多租戶系統出現資源爭用時，cell 邊界先決定故障能擴散到哪裡。當容量壓力開始拉高時，shuffle sharding 讓風險分散到不同子集合，避免單一熱點把整個服務拖進同一個失敗模式。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否指出一個 workload 的 blast radius 邊界&lt;/li>
&lt;li>能否把共享基礎設施切成可獨立恢復的 cell&lt;/li>
&lt;li>能否說明 contention 會落在哪個 shard&lt;/li>
&lt;li>能否把 recovery 設計成分批恢復，而不是一次全開&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Amazon 的重點是把隔離變成架構語言，這和 Meta 的 region failover、Shopify 的 pod 架構、GCP 的控制面邊界都在同一條線上。差別只在於 Amazon 更早把 cell 與 shard 語言標準化，所以特別適合用來反推其他大型平台的設計選擇。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>cell-based architecture 讓一個 cell 壞掉時，其他 cell 仍能維持服務。&lt;/li>
&lt;li>shuffle sharding 將多租戶請求分散到不同子集合，限制單一客戶或單一熱點的擴散範圍。&lt;/li>
&lt;li>static stability 讓 control plane 失效時 data plane 仍可服務。&lt;/li>
&lt;li>constant work pattern 避免失敗模式下的額外放大成本。&lt;/li>
&lt;li>workload isolation 讓 tenancy / region / AZ 的邊界能各自承擔風險。&lt;/li>
&lt;li>failure containment 讓擴散先停在 cell 或 shard 邊界。&lt;/li>
&lt;li>push-based recovery 讓恢復節奏不依賴大規模同步操作。&lt;/li>
&lt;li>fault isolation 讓局部失效不會拖垮整個 fleet。&lt;/li>
&lt;li>constant work 讓 failure mode 不會因為多做一件事而繼續放大。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-builders-library/">Introducing The Amazon Builders’ Library&lt;/a>：Builders&amp;rsquo; Library 的官方入口。&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/">Workload isolation using shuffle-sharding&lt;/a>：shuffle sharding 與 fault isolation 的官方文章。&lt;/li>
&lt;li>&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/faq.html">FAQ - Reducing the Scope of Impact with Cell-Based Architecture&lt;/a>：cell-based architecture 與 shuffle sharding 的關係說明。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Amazon 是 cell-based architecture 與 shuffle sharding 的代表、AWS Builders&rsquo; Library 是大規模分散式系統的工程實踐 SSoT。教學重點在「如何設計才能讓失效局部化」。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Cell-based Architecture：把服務切成獨立 cell、每個 cell 有完整 stack</li>
<li>Shuffle Sharding：客戶請求映射到 cell 的隨機切分、讓單一壞客戶無法擊倒所有 cell</li>
<li>Static Stability：control plane 失效時 data plane 仍能服務</li>
<li>Constant Work Pattern：avoid scaling traffic in failure modes</li>
<li>AWS Builders&rsquo; Library：可重用 reliability patterns 的官方文件</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cell-based Architecture</td>
          <td>DynamoDB / Route 53 / S3 的 cell 劃分原則</td>
      </tr>
      <tr>
          <td>Shuffle Sharding</td>
          <td>數學上的 blast radius 量化</td>
      </tr>
      <tr>
          <td>Static Stability</td>
          <td>control / data plane 分離的設計取捨</td>
      </tr>
      <tr>
          <td>Workload Isolation</td>
          <td>tenancy / region / availability zone 的隔離層級</td>
      </tr>
      <tr>
          <td>Build with constant work</td>
          <td>為何 push-based 比 pull-based 在 failure 時更穩定</td>
      </tr>
  </tbody>
</table>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1</a></td>
          <td>Shuffle Sharding 與 Cell 邊界</td>
          <td>用局部隔離限制多租戶擴散，讓恢復可以分批收斂</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">A2</a></td>
          <td>Static Stability 與 Constant Work Pattern</td>
          <td>控制面失效時資料面用快取與固定工作量維持服務</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Amazon 這個案例在講的是可靠性如何靠隔離來守住擴散邊界。讀者先看懂 cell-based architecture 與 shuffle sharding 的責任，再把它們當成控制 blast radius 的設計語言，而不是單純的 AWS 名詞。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當多租戶系統出現資源爭用時，cell 邊界先決定故障能擴散到哪裡。當容量壓力開始拉高時，shuffle sharding 讓風險分散到不同子集合，避免單一熱點把整個服務拖進同一個失敗模式。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否指出一個 workload 的 blast radius 邊界</li>
<li>能否把共享基礎設施切成可獨立恢復的 cell</li>
<li>能否說明 contention 會落在哪個 shard</li>
<li>能否把 recovery 設計成分批恢復，而不是一次全開</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Amazon 的重點是把隔離變成架構語言，這和 Meta 的 region failover、Shopify 的 pod 架構、GCP 的控制面邊界都在同一條線上。差別只在於 Amazon 更早把 cell 與 shard 語言標準化，所以特別適合用來反推其他大型平台的設計選擇。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>cell-based architecture 讓一個 cell 壞掉時，其他 cell 仍能維持服務。</li>
<li>shuffle sharding 將多租戶請求分散到不同子集合，限制單一客戶或單一熱點的擴散範圍。</li>
<li>static stability 讓 control plane 失效時 data plane 仍可服務。</li>
<li>constant work pattern 避免失敗模式下的額外放大成本。</li>
<li>workload isolation 讓 tenancy / region / AZ 的邊界能各自承擔風險。</li>
<li>failure containment 讓擴散先停在 cell 或 shard 邊界。</li>
<li>push-based recovery 讓恢復節奏不依賴大規模同步操作。</li>
<li>fault isolation 讓局部失效不會拖垮整個 fleet。</li>
<li>constant work 讓 failure mode 不會因為多做一件事而繼續放大。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-builders-library/">Introducing The Amazon Builders’ Library</a>：Builders&rsquo; Library 的官方入口。</li>
<li><a href="https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/">Workload isolation using shuffle-sharding</a>：shuffle sharding 與 fault isolation 的官方文章。</li>
<li><a href="https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/faq.html">FAQ - Reducing the Scope of Impact with Cell-Based Architecture</a>：cell-based architecture 與 shuffle sharding 的關係說明。</li>
</ul>
]]></content:encoded></item><item><title>GitHub</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/</guid><description>&lt;p>GitHub 是高 traffic、跨區資料庫 + 強一致性需求的代表、MySQL split-brain / Actions 大規模 outage 是跨區資料一致性與 control-plane 失效的教學標竿。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>MySQL 跨區拓撲：master / replica / Orchestrator 自動切換的失敗模式&lt;/li>
&lt;li>Split-brain 復原：為何資料一致性復原比可用性復原更耗時&lt;/li>
&lt;li>Actions / Codespaces 等控制面：使用者面 outage 與 control plane 的關係&lt;/li>
&lt;li>通訊節奏：GitHub status page / blog 的事故揭露文化&lt;/li>
&lt;/ul>
&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>2018-10&lt;/td>
 &lt;td>MySQL split-brain 24 小時&lt;/td>
 &lt;td>Orchestrator 自動 failover 失誤、人工干預延遲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2020-11&lt;/td>
 &lt;td>Actions outages&lt;/td>
 &lt;td>CI/CD 平台失效的客戶影響量化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2021-11&lt;/td>
 &lt;td>跨區網路 / replication&lt;/td>
 &lt;td>跨區一致性 vs 可用性的取捨&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>GitHub 這個案例在講的是跨區資料一致性如何把事故拉長。讀者先看懂 replication、Orchestrator 與 status communication 的責任，再把 split-brain 與 Actions outage 視為不同層級的 control-plane 失效。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 replication lag 或 schema 變更讓資料庫進入不穩定狀態時，恢復速度會被一致性約束拉慢。當使用者面產品也同時掛掉時，狀態頁與事故報告就成了對外與對內的共同路由，讓時間線保持一致。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否說明哪個節點持有權威寫入&lt;/li>
&lt;li>能否區分自動 failover 與人工切換的責任邊界&lt;/li>
&lt;li>能否把事故時間線寫成對外可理解的 status update&lt;/li>
&lt;li>能否把 Actions 這類控制面事故量化成客戶影響&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>GitHub 和 Atlassian、Microsoft 365 的共通點，是都把「對外說明」與「內部復原」綁在一起。它也能和 Azure AD 對照，因為一旦身份或 replication 的控制面退化，後面所有產品層的恢復都會被拉長。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2018-10 split-brain 事故說明權威寫入與人工切換的邊界。&lt;/li>
&lt;li>2020-11 Actions outage 與 2021-11 replication 問題則展示了控制面失效如何影響客戶體感與恢復時間。&lt;/li>
&lt;li>replication lag、schema migration 與 read replica deadlock 都屬於相近失敗面。&lt;/li>
&lt;li>status report 的寫法本身也是事故管理能力的一部分。&lt;/li>
&lt;li>orchestrator 自動切換失敗讓自動化與人工介入的邊界更明顯。&lt;/li>
&lt;li>control-plane outage 會同時影響 CI/CD 與資料服務的信任感。&lt;/li>
&lt;li>code hosting 與 CI/CD 共享控制面，讓一個事故同時影響多種使用情境。&lt;/li>
&lt;li>read replica deadlock 讓 schema 變更也成為事故起點。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.blog/2018-10-30-oct21-post-incident-analysis/">October 21 post-incident analysis&lt;/a>：GitHub 2018 年資料庫與 replication 事故的深度分析。&lt;/li>
&lt;li>&lt;a href="https://github.blog/2020-12-02-availability-report-november-2020/">GitHub Availability Report: November 2020&lt;/a>：MySQL replication lag 與 Actions 事故的官方報告。&lt;/li>
&lt;li>&lt;a href="https://github.blog/news-insights/company-news/github-availability-report-december-2020/">GitHub Availability Report: December 2020&lt;/a>：November incident 的後續說明。&lt;/li>
&lt;li>&lt;a href="https://github.blog/news-insights/company-news/github-availability-report-november-2021/">GitHub Availability Report: November 2021&lt;/a>：schema migration / MySQL read replica deadlock 的官方報告。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>GitHub 是高 traffic、跨區資料庫 + 強一致性需求的代表、MySQL split-brain / Actions 大規模 outage 是跨區資料一致性與 control-plane 失效的教學標竿。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>MySQL 跨區拓撲：master / replica / Orchestrator 自動切換的失敗模式</li>
<li>Split-brain 復原：為何資料一致性復原比可用性復原更耗時</li>
<li>Actions / Codespaces 等控制面：使用者面 outage 與 control plane 的關係</li>
<li>通訊節奏：GitHub status page / blog 的事故揭露文化</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2018-10</td>
          <td>MySQL split-brain 24 小時</td>
          <td>Orchestrator 自動 failover 失誤、人工干預延遲</td>
      </tr>
      <tr>
          <td>2020-11</td>
          <td>Actions outages</td>
          <td>CI/CD 平台失效的客戶影響量化</td>
      </tr>
      <tr>
          <td>2021-11</td>
          <td>跨區網路 / replication</td>
          <td>跨區一致性 vs 可用性的取捨</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>GitHub 這個案例在講的是跨區資料一致性如何把事故拉長。讀者先看懂 replication、Orchestrator 與 status communication 的責任，再把 split-brain 與 Actions outage 視為不同層級的 control-plane 失效。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 replication lag 或 schema 變更讓資料庫進入不穩定狀態時，恢復速度會被一致性約束拉慢。當使用者面產品也同時掛掉時，狀態頁與事故報告就成了對外與對內的共同路由，讓時間線保持一致。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否說明哪個節點持有權威寫入</li>
<li>能否區分自動 failover 與人工切換的責任邊界</li>
<li>能否把事故時間線寫成對外可理解的 status update</li>
<li>能否把 Actions 這類控制面事故量化成客戶影響</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>GitHub 和 Atlassian、Microsoft 365 的共通點，是都把「對外說明」與「內部復原」綁在一起。它也能和 Azure AD 對照，因為一旦身份或 replication 的控制面退化，後面所有產品層的恢復都會被拉長。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2018-10 split-brain 事故說明權威寫入與人工切換的邊界。</li>
<li>2020-11 Actions outage 與 2021-11 replication 問題則展示了控制面失效如何影響客戶體感與恢復時間。</li>
<li>replication lag、schema migration 與 read replica deadlock 都屬於相近失敗面。</li>
<li>status report 的寫法本身也是事故管理能力的一部分。</li>
<li>orchestrator 自動切換失敗讓自動化與人工介入的邊界更明顯。</li>
<li>control-plane outage 會同時影響 CI/CD 與資料服務的信任感。</li>
<li>code hosting 與 CI/CD 共享控制面，讓一個事故同時影響多種使用情境。</li>
<li>read replica deadlock 讓 schema 變更也成為事故起點。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.blog/2018-10-30-oct21-post-incident-analysis/">October 21 post-incident analysis</a>：GitHub 2018 年資料庫與 replication 事故的深度分析。</li>
<li><a href="https://github.blog/2020-12-02-availability-report-november-2020/">GitHub Availability Report: November 2020</a>：MySQL replication lag 與 Actions 事故的官方報告。</li>
<li><a href="https://github.blog/news-insights/company-news/github-availability-report-december-2020/">GitHub Availability Report: December 2020</a>：November incident 的後續說明。</li>
<li><a href="https://github.blog/news-insights/company-news/github-availability-report-november-2021/">GitHub Availability Report: November 2021</a>：schema migration / MySQL read replica deadlock 的官方報告。</li>
</ul>
]]></content:encoded></item><item><title>8.3 Dropbox：從 Python 遷移到 Go</title><link>https://tarrragon.github.io/blog/go/08-case-studies/dropbox/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/dropbox/</guid><description>&lt;p>Dropbox 的案例是最典型的「性能關鍵服務遷移」故事之一。官方案例直接寫到，他們把 performance-critical backends 從 Python 轉到 Go，以獲得更好的 concurrency support 與更快的執行速度。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/dropbox">Dropbox - Open sourcing our Go libraries&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很常被選在 Python 已經不夠用的後端邊界。&lt;/li>
&lt;li>併發支援通常是遷移的重要原因之一。&lt;/li>
&lt;li>遷移通常先把性能最敏感的部分換成 Go，逐步擴展。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/dropbox/godropbox">dropbox/godropbox&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/dropbox/dropbox-api-spec">dropbox/dropbox-api-spec&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Dropbox 的公開 Go libraries 與 API spec 很適合對照閱讀。你會看到一個大公司如何把 Go 用在可重用工具與服務邊界上。&lt;/p></description><content:encoded><![CDATA[<p>Dropbox 的案例是最典型的「性能關鍵服務遷移」故事之一。官方案例直接寫到，他們把 performance-critical backends 從 Python 轉到 Go，以獲得更好的 concurrency support 與更快的執行速度。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/dropbox">Dropbox - Open sourcing our Go libraries</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很常被選在 Python 已經不夠用的後端邊界。</li>
<li>併發支援通常是遷移的重要原因之一。</li>
<li>遷移通常先把性能最敏感的部分換成 Go，逐步擴展。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/dropbox/godropbox">dropbox/godropbox</a></li>
<li><a href="https://github.com/dropbox/dropbox-api-spec">dropbox/dropbox-api-spec</a></li>
</ul>
<p>Dropbox 的公開 Go libraries 與 API spec 很適合對照閱讀。你會看到一個大公司如何把 Go 用在可重用工具與服務邊界上。</p>
]]></content:encoded></item><item><title>案例：pybind11 綁定 C++ 類別</title><link>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/pybind11-cpp-binding/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/pybind11-cpp-binding/</guid><description>&lt;p>本案例展示如何使用 pybind11 將 C++ 類別完整綁定到 Python，包含建構子、方法、屬性、運算子重載，以及記憶體管理與生命週期控制。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/pybind11/" data-link-title="4.3 pybind11：現代 C&amp;#43;&amp;#43; 綁定" data-link-desc="使用 pybind11 建立 Python 與 C&amp;#43;&amp;#43; 的綁定">4.3 pybind11：現代 C++ 綁定&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="使用情境">使用情境&lt;/h3>
&lt;p>在以下情境中，你可能需要在 Python 中使用 C++ 類別：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>複用現有 C++ 程式庫&lt;/strong>：公司有成熟的 C++ 資料結構或演算法，想在 Python 專案中使用&lt;/li>
&lt;li>&lt;strong>效能敏感的資料處理&lt;/strong>：需要高效的記憶體管理和計算效能&lt;/li>
&lt;li>&lt;strong>自訂資料結構&lt;/strong>：標準 Python 容器無法滿足特定需求&lt;/li>
&lt;/ol>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;p>本案例將建立一個 &lt;code>StringProcessor&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">StringProcessor 功能：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── 建構子：支援預設和參數化初始化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── 方法：字串處理（大小寫轉換、統計、搜尋）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── 屬性：可讀寫的狀態屬性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── 運算子重載：+ 運算子串接、[] 索引存取
&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="實作步驟">實作步驟&lt;/h2>
&lt;h3 id="步驟-1專案結構">步驟 1：專案結構&lt;/h3>





&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">pybind11_string_processor/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── setup.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ ├── string_processor.hpp # C++ 標頭檔
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ ├── string_processor.cpp # C++ 實作
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ └── bindings.cpp # pybind11 綁定
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ └── test_string_processor.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── benchmark.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-2c-類別定義">步驟 2：C++ 類別定義&lt;/h3>
&lt;p>首先，建立 C++ 類別的標頭檔：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// src/string_processor.hpp
&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="cp">#ifndef STRING_PROCESSOR_HPP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="cp">#define STRING_PROCESSOR_HPP
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;unordered_map&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;memory&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdexcept&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">&lt;span class="cm"> * StringProcessor: 高效能字串處理類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="cm"> * 提供字串操作、統計分析和搜尋功能。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="cm"> * 設計用於展示 pybind11 的類別綁定特性。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">StringProcessor&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 建構子與解構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 預設建構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 參數化建構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">explicit&lt;/span> &lt;span class="nf">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 複製建構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 移動建構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">noexcept&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 解構子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 基本方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 取得內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&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"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 設定內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">set_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 取得長度
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="nf">length&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&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"> 50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 是否為空
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">empty&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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"> 53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 字串處理方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 轉換為大寫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">to_upper&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 轉換為小寫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">to_lower&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 反轉字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">reverse&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 移除前後空白
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">trim&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 分割字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">delimiter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 取代子字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">old_str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">new_str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 統計分析方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 字元頻率統計
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unordered_map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">char&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">char_frequency&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 單字計數
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="nf">word_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 子字串出現次數
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="nf">count_occurrences&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 搜尋方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 搜尋子字串位置（找不到回傳 -1）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="nf">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 搜尋所有出現位置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">size_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">find_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 是否包含子字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 是否以指定字串開頭
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">starts_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">prefix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 是否以指定字串結尾
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="nf">ends_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">suffix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 運算子重載
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl"> &lt;span class="c1">// + 運算子：串接
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StringProcessor&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl"> &lt;span class="c1">// += 運算子：原地串接
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="c1">// [] 運算子：索引存取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="p">[](&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="c1">// == 運算子：相等比較
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="c1">// != 運算子：不等比較
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl"> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 指派運算子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="k">operator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">noexcept&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 處理計數器（用於展示狀態追蹤）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">mutable&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">operation_count_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 輔助方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">increment_operation_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">++&lt;/span>&lt;span class="n">operation_count_&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">143&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 取得操作計數（用於效能分析）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">operation_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">operation_count_&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">147&lt;/span>&lt;span class="cl"> &lt;span class="kt">void&lt;/span> &lt;span class="nf">reset_operation_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">operation_count_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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">148&lt;/span>&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl">&lt;span class="cp">#endif &lt;/span>&lt;span class="c1">// STRING_PROCESSOR_HPP
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-3c-實作">步驟 3：C++ 實作&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// src/string_processor.cpp
&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="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;string_processor.hpp&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;algorithm&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cctype&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;sstream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&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"> 12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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"> 15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">operation_count_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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"> 18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">noexcept&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">move&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">)),&lt;/span> &lt;span class="n">operation_count_&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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"> 21&lt;/span>&lt;span class="cl"> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::~&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl">&lt;span class="c1">// 基本方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">set_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">content&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"> 31&lt;/span>&lt;span class="cl"> &lt;span class="n">content_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="c1">// 字串處理方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">to_upper&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">transform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">end&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="p">[](&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">toupper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&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"> 44&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">to_lower&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">transform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">end&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="p">[](&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">tolower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&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"> 52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">begin&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">end&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">trim&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find_first_not_of&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; &lt;/span>&lt;span class="se">\t\n\r\f\v&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">start&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&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"> 66&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find_last_not_of&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; &lt;/span>&lt;span class="se">\t\n\r\f\v&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">substr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">delimiter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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"> 77&lt;/span>&lt;span class="cl"> &lt;span class="c1">// 空分隔符：按字元分割
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">char&lt;/span> &lt;span class="nl">c&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">content_&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"> 79&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">push_back&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&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"> 88&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">push_back&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">substr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">delimiter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">end&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delimiter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">push_back&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">substr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">old_str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">new_str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">old_str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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">101&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">old_str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pos&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&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">107&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pos&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">old_str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">new_str&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="n">pos&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">new_str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl">&lt;span class="c1">// 統計分析方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unordered_map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">char&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">char_frequency&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">unordered_map&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">char&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">freq&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">char&lt;/span> &lt;span class="nl">c&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">content_&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">121&lt;/span>&lt;span class="cl"> &lt;span class="n">freq&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">freq&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="n">size_t&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">word_count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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">129&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">istringstream&lt;/span> &lt;span class="n">iss&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">word&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">iss&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">word&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">136&lt;/span>&lt;span class="cl"> &lt;span class="n">count&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">141&lt;/span>&lt;span class="cl">&lt;span class="n">size_t&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">count_occurrences&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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">144&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">147&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pos&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&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">150&lt;/span>&lt;span class="cl"> &lt;span class="n">count&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl"> &lt;span class="n">pos&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">153&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl">&lt;span class="c1">// 搜尋方法
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">161&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">162&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">163&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">pos&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">static_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">164&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">165&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">166&lt;/span>&lt;span class="cl">&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">size_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">find_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">167&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">168&lt;/span>&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">size_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">positions&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">169&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">empty&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">170&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">positions&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">171&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">172&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">173&lt;/span>&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">174&lt;/span>&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pos&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&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">175&lt;/span>&lt;span class="cl"> &lt;span class="n">positions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">push_back&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pos&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">176&lt;/span>&lt;span class="cl"> &lt;span class="n">pos&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">177&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">178&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">positions&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">179&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">180&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">181&lt;/span>&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">182&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">183&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">find&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">substring&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">npos&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">184&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">185&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">186&lt;/span>&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">starts_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">prefix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">187&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">188&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">prefix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&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">189&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">190&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">191&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">prefix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">prefix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">192&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">193&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">194&lt;/span>&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">ends_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">suffix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">195&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">196&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">suffix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&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">197&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">198&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">199&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">suffix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">200&lt;/span>&lt;span class="cl"> &lt;span class="n">suffix&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">suffix&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">201&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">202&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">203&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">204&lt;/span>&lt;span class="cl">&lt;span class="c1">// 運算子重載
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">205&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">206&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">207&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">208&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">209&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">StringProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">210&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">211&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">212&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">+=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&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">213&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">214&lt;/span>&lt;span class="cl"> &lt;span class="n">content_&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">215&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">216&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">217&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">218&lt;/span>&lt;span class="cl">&lt;span class="kt">char&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="p">[](&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">219&lt;/span>&lt;span class="cl"> &lt;span class="n">increment_operation_count&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">220&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&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">221&lt;/span>&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">out_of_range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Index out of range: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">to_string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">222&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">223&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">224&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">225&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">226&lt;/span>&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">==&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">227&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">228&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">229&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">230&lt;/span>&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">231&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">content_&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">232&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">233&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">234&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">235&lt;/span>&lt;span class="cl">&lt;span class="c1">// 指派運算子
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">236&lt;/span>&lt;span class="cl">&lt;span class="c1">// ========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">237&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">238&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">other&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">239&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">other&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">240&lt;/span>&lt;span class="cl"> &lt;span class="n">content_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">241&lt;/span>&lt;span class="cl"> &lt;span class="n">operation_count_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">242&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">243&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">244&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">245&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">246&lt;/span>&lt;span class="cl">&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="k">operator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StringProcessor&lt;/span>&lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">noexcept&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">247&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">other&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">248&lt;/span>&lt;span class="cl"> &lt;span class="n">content_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">move&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">249&lt;/span>&lt;span class="cl"> &lt;span class="n">operation_count_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">250&lt;/span>&lt;span class="cl"> &lt;span class="n">other&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">content_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">251&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">252&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">253&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-4pybind11-綁定">步驟 4：pybind11 綁定&lt;/h3>
&lt;p>這是最關鍵的部分，將 C++ 類別暴露給 Python：&lt;/p></description><content:encoded><![CDATA[<p>本案例展示如何使用 pybind11 將 C++ 類別完整綁定到 Python，包含建構子、方法、屬性、運算子重載，以及記憶體管理與生命週期控制。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/05-c-extensions/pybind11/" data-link-title="4.3 pybind11：現代 C&#43;&#43; 綁定" data-link-desc="使用 pybind11 建立 Python 與 C&#43;&#43; 的綁定">4.3 pybind11：現代 C++ 綁定</a></li>
<li><a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="使用情境">使用情境</h3>
<p>在以下情境中，你可能需要在 Python 中使用 C++ 類別：</p>
<ol>
<li><strong>複用現有 C++ 程式庫</strong>：公司有成熟的 C++ 資料結構或演算法，想在 Python 專案中使用</li>
<li><strong>效能敏感的資料處理</strong>：需要高效的記憶體管理和計算效能</li>
<li><strong>自訂資料結構</strong>：標準 Python 容器無法滿足特定需求</li>
</ol>
<h3 id="設計目標">設計目標</h3>
<p>本案例將建立一個 <code>StringProcessor</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">StringProcessor 功能：
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 建構子：支援預設和參數化初始化
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── 方法：字串處理（大小寫轉換、統計、搜尋）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── 屬性：可讀寫的狀態屬性
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── 運算子重載：+ 運算子串接、[] 索引存取
</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></span></code></pre></div><h2 id="實作步驟">實作步驟</h2>
<h3 id="步驟-1專案結構">步驟 1：專案結構</h3>





<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">pybind11_string_processor/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── CMakeLists.txt
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── setup.py
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   ├── string_processor.hpp    # C++ 標頭檔
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│   ├── string_processor.cpp    # C++ 實作
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   └── bindings.cpp            # pybind11 綁定
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── tests/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   └── test_string_processor.py
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── benchmark.py</span></span></code></pre></div><h3 id="步驟-2c-類別定義">步驟 2：C++ 類別定義</h3>
<p>首先，建立 C++ 類別的標頭檔：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1">// src/string_processor.hpp
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="c1"></span><span class="cp">#ifndef STRING_PROCESSOR_HPP
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="cp">#define STRING_PROCESSOR_HPP
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;vector&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;unordered_map&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;memory&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdexcept&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="cm"> * StringProcessor: 高效能字串處理類別
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="cm"> * 提供字串操作、統計分析和搜尋功能。
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="cm"> * 設計用於展示 pybind11 的類別綁定特性。
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">StringProcessor</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c1"></span>    <span class="c1">// 建構子與解構子
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="c1">// 預設建構子
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="c1"></span>    <span class="n">StringProcessor</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="c1">// 參數化建構子
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="c1"></span>    <span class="k">explicit</span> <span class="nf">StringProcessor</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">content</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="c1">// 複製建構子
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="c1"></span>    <span class="n">StringProcessor</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="c1">// 移動建構子
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="c1"></span>    <span class="n">StringProcessor</span><span class="p">(</span><span class="n">StringProcessor</span><span class="o">&amp;&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">noexcept</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="c1">// 解構子
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"></span>    <span class="o">~</span><span class="n">StringProcessor</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="c1"></span>    <span class="c1">// 基本方法
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="c1">// 取得內容
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="c1"></span>    <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">content</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">content_</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="c1">// 設定內容
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="c1"></span>    <span class="kt">void</span> <span class="nf">set_content</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">content</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="c1">// 取得長度
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="c1"></span>    <span class="n">size_t</span> <span class="nf">length</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">length</span><span class="p">();</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="c1">// 是否為空
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="nf">empty</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="c1"></span>    <span class="c1">// 字串處理方法
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="c1">// 轉換為大寫
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">to_upper</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="c1">// 轉換為小寫
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">to_lower</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1">// 反轉字串
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">reverse</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="c1">// 移除前後空白
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">trim</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="c1">// 分割字串
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">split</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">delimiter</span> <span class="o">=</span> <span class="s">&#34; &#34;</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="c1">// 取代子字串
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">replace</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">old_str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">new_str</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="c1"></span>    <span class="c1">// 統計分析方法
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="c1">// 字元頻率統計
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="kt">char</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;</span> <span class="n">char_frequency</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="c1">// 單字計數
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="c1"></span>    <span class="n">size_t</span> <span class="nf">word_count</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="c1">// 子字串出現次數
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="c1"></span>    <span class="n">size_t</span> <span class="nf">count_occurrences</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="c1"></span>    <span class="c1">// 搜尋方法
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="c1">// 搜尋子字串位置（找不到回傳 -1）
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="c1"></span>    <span class="kt">int</span> <span class="nf">find</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">,</span> <span class="n">size_t</span> <span class="n">start</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="c1">// 搜尋所有出現位置
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="c1"></span>    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">size_t</span><span class="o">&gt;</span> <span class="n">find_all</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="c1">// 是否包含子字串
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="nf">contains</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="c1">// 是否以指定字串開頭
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="nf">starts_with</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">prefix</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="c1">// 是否以指定字串結尾
</span></span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="nf">ends_with</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">suffix</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="c1"></span>    <span class="c1">// 運算子重載
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="c1">// + 運算子：串接
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="c1"></span>    <span class="n">StringProcessor</span> <span class="k">operator</span><span class="o">+</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="c1">// += 運算子：原地串接
</span></span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="c1"></span>    <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="k">operator</span><span class="o">+=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="c1">// [] 運算子：索引存取
</span></span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="c1"></span>    <span class="kt">char</span> <span class="k">operator</span><span class="p">[](</span><span class="n">size_t</span> <span class="n">index</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="c1">// == 運算子：相等比較
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="k">operator</span><span class="o">==</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="c1">// != 運算子：不等比較
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="c1"></span>    <span class="kt">bool</span> <span class="k">operator</span><span class="o">!=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="c1"></span>    <span class="c1">// 指派運算子
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="n">StringProcessor</span><span class="o">&amp;&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">noexcept</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="c1">// 處理計數器（用於展示狀態追蹤）
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="c1"></span>    <span class="k">mutable</span> <span class="n">size_t</span> <span class="n">operation_count_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="c1">// 輔助方法
</span></span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"></span>    <span class="kt">void</span> <span class="nf">increment_operation_count</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="o">++</span><span class="n">operation_count_</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="c1">// 取得操作計數（用於效能分析）
</span></span></span><span class="line"><span class="ln">146</span><span class="cl"><span class="c1"></span>    <span class="n">size_t</span> <span class="n">operation_count</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">operation_count_</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="kt">void</span> <span class="nf">reset_operation_count</span><span class="p">()</span> <span class="p">{</span> <span class="n">operation_count_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="cp">#endif </span><span class="c1">// STRING_PROCESSOR_HPP
</span></span></span></code></pre></div><h3 id="步驟-3c-實作">步驟 3：C++ 實作</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1">// src/string_processor.cpp
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&#34;string_processor.hpp&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;algorithm&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;cctype&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sstream&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="c1">// 建構子與解構子
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="n">StringProcessor</span><span class="o">::</span><span class="n">StringProcessor</span><span class="p">()</span> <span class="o">:</span> <span class="n">content_</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="n">StringProcessor</span><span class="o">::</span><span class="n">StringProcessor</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="o">:</span> <span class="n">content_</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="n">StringProcessor</span><span class="o">::</span><span class="n">StringProcessor</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="o">:</span> <span class="n">content_</span><span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">),</span> <span class="n">operation_count_</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="n">StringProcessor</span><span class="o">::</span><span class="n">StringProcessor</span><span class="p">(</span><span class="n">StringProcessor</span><span class="o">&amp;&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">noexcept</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="o">:</span> <span class="n">content_</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">)),</span> <span class="n">operation_count_</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="n">StringProcessor</span><span class="o">::~</span><span class="n">StringProcessor</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="c1">// 基本方法
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="kt">void</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">set_content</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">content</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="n">content_</span> <span class="o">=</span> <span class="n">content</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1">// 字串處理方法
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">to_upper</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">result</span> <span class="o">=</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">transform</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">result</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">result</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                   <span class="p">[](</span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">toupper</span><span class="p">(</span><span class="n">c</span><span class="p">);</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">to_lower</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">result</span> <span class="o">=</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">transform</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">result</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">result</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">                   <span class="p">[](</span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">tolower</span><span class="p">(</span><span class="n">c</span><span class="p">);</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">reverse</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">result</span> <span class="o">=</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">reverse</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">result</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">trim</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="n">size_t</span> <span class="n">start</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find_first_not_of</span><span class="p">(</span><span class="s">&#34; </span><span class="se">\t\n\r\f\v</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">start</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="n">size_t</span> <span class="n">end</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find_last_not_of</span><span class="p">(</span><span class="s">&#34; </span><span class="se">\t\n\r\f\v</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">split</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">delimiter</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">delimiter</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="c1">// 空分隔符：按字元分割
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="c1"></span>        <span class="k">for</span> <span class="p">(</span><span class="kt">char</span> <span class="nl">c</span> <span class="p">:</span> <span class="n">content_</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">result</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">c</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="n">size_t</span> <span class="n">start</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="n">size_t</span> <span class="n">end</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">delimiter</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">end</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="n">result</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">content_</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">end</span> <span class="o">+</span> <span class="n">delimiter</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">end</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">delimiter</span><span class="p">,</span> <span class="n">start</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="n">result</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">content_</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">start</span><span class="p">));</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">replace</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">old_str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                                     <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">new_str</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">old_str</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">return</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">result</span> <span class="o">=</span> <span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">    <span class="n">size_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="k">while</span> <span class="p">((</span><span class="n">pos</span> <span class="o">=</span> <span class="n">result</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">old_str</span><span class="p">,</span> <span class="n">pos</span><span class="p">))</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">result</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">old_str</span><span class="p">.</span><span class="n">length</span><span class="p">(),</span> <span class="n">new_str</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="n">pos</span> <span class="o">+=</span> <span class="n">new_str</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="c1">// 統計分析方法
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="kt">char</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">char_frequency</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="kt">char</span><span class="p">,</span> <span class="kt">int</span><span class="o">&gt;</span> <span class="n">freq</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">char</span> <span class="nl">c</span> <span class="p">:</span> <span class="n">content_</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="n">freq</span><span class="p">[</span><span class="n">c</span><span class="p">]</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">return</span> <span class="n">freq</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="n">size_t</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">word_count</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">content_</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">istringstream</span> <span class="n">iss</span><span class="p">(</span><span class="n">content_</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">size_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">word</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">iss</span> <span class="o">&gt;&gt;</span> <span class="n">word</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="n">count</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="k">return</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="n">size_t</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">count_occurrences</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">substring</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="n">size_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="n">size_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">while</span> <span class="p">((</span><span class="n">pos</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">,</span> <span class="n">pos</span><span class="p">))</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="n">count</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">pos</span> <span class="o">+=</span> <span class="n">substring</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="k">return</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="c1">// 搜尋方法
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="kt">int</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">find</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">,</span> <span class="n">size_t</span> <span class="n">start</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">size_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">,</span> <span class="n">start</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">pos</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="o">?</span> <span class="o">-</span><span class="mi">1</span> <span class="o">:</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">size_t</span><span class="o">&gt;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">find_all</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">size_t</span><span class="o">&gt;</span> <span class="n">positions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">substring</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="k">return</span> <span class="n">positions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">size_t</span> <span class="n">pos</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="k">while</span> <span class="p">((</span><span class="n">pos</span> <span class="o">=</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">,</span> <span class="n">pos</span><span class="p">))</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">        <span class="n">positions</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="n">pos</span> <span class="o">+=</span> <span class="n">substring</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="k">return</span> <span class="n">positions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">
</span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="kt">bool</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">contains</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">substring</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">)</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="kt">bool</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">starts_with</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">prefix</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">prefix</span><span class="p">.</span><span class="n">length</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">content_</span><span class="p">.</span><span class="n">length</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">compare</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">prefix</span><span class="p">.</span><span class="n">length</span><span class="p">(),</span> <span class="n">prefix</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="kt">bool</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="n">ends_with</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">suffix</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">suffix</span><span class="p">.</span><span class="n">length</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">content_</span><span class="p">.</span><span class="n">length</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span><span class="p">.</span><span class="n">compare</span><span class="p">(</span><span class="n">content_</span><span class="p">.</span><span class="n">length</span><span class="p">()</span> <span class="o">-</span> <span class="n">suffix</span><span class="p">.</span><span class="n">length</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">                            <span class="n">suffix</span><span class="p">.</span><span class="n">length</span><span class="p">(),</span> <span class="n">suffix</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">204</span><span class="cl"><span class="c1">// 運算子重載
</span></span></span><span class="line"><span class="ln">205</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">206</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="n">StringProcessor</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">+</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="k">return</span> <span class="nf">StringProcessor</span><span class="p">(</span><span class="n">content_</span> <span class="o">+</span> <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">
</span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">+=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">    <span class="n">content_</span> <span class="o">+=</span> <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">    <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">
</span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="kt">char</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="p">[](</span><span class="n">size_t</span> <span class="n">index</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="n">increment_operation_count</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="o">&gt;=</span> <span class="n">content_</span><span class="p">.</span><span class="n">length</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">out_of_range</span><span class="p">(</span><span class="s">&#34;Index out of range: &#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">index</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">
</span></span><span class="line"><span class="ln">226</span><span class="cl"><span class="kt">bool</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">==</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span> <span class="o">==</span> <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl"><span class="kt">bool</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">!=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">    <span class="k">return</span> <span class="n">content_</span> <span class="o">!=</span> <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="c1">// 指派運算子
</span></span></span><span class="line"><span class="ln">236</span><span class="cl"><span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">237</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">238</span><span class="cl"><span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="k">this</span> <span class="o">!=</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="n">content_</span> <span class="o">=</span> <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="n">operation_count_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">    <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="n">StringProcessor</span><span class="o">&amp;&amp;</span> <span class="n">other</span><span class="p">)</span> <span class="k">noexcept</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="k">this</span> <span class="o">!=</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="n">content_</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="n">operation_count_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">        <span class="n">other</span><span class="p">.</span><span class="n">content_</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">    <span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="步驟-4pybind11-綁定">步驟 4：pybind11 綁定</h3>
<p>這是最關鍵的部分，將 C++ 類別暴露給 Python：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1">// src/bindings.cpp
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&lt;pybind11/pybind11.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;pybind11/stl.h&gt;</span><span class="cp">  </span><span class="c1">// 支援 STL 容器自動轉換
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="c1"></span><span class="cp">#include</span> <span class="cpf">&lt;pybind11/operators.h&gt;</span><span class="cp">  </span><span class="c1">// 支援運算子重載
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;string_processor.hpp&#34;</span><span class="cp">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="k">namespace</span> <span class="n">py</span> <span class="o">=</span> <span class="n">pybind11</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="n">PYBIND11_MODULE</span><span class="p">(</span><span class="n">string_processor</span><span class="p">,</span> <span class="n">m</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">m</span><span class="p">.</span><span class="n">doc</span><span class="p">()</span> <span class="o">=</span> <span class="s">&#34;StringProcessor: 高效能字串處理模組&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1"></span>    <span class="c1">// 類別綁定
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="c1"></span>    <span class="n">py</span><span class="o">::</span><span class="n">class_</span><span class="o">&lt;</span><span class="n">StringProcessor</span><span class="o">&gt;</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="s">&#34;StringProcessor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="c1"></span>        <span class="c1">// 建構子
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">init</span><span class="o">&lt;&gt;</span><span class="p">(),</span> <span class="s">&#34;建立空的 StringProcessor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">init</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;&gt;</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;content&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">             <span class="s">&#34;使用指定內容建立 StringProcessor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="c1">// 複製建構（Python 的 copy 模組會使用）
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">init</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;&gt;</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="c1"></span>        <span class="c1">// 屬性
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"></span>        <span class="c1">// content 屬性：可讀寫
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def_property</span><span class="p">(</span><span class="s">&#34;content&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">                      <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">                      <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">set_content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">                      <span class="s">&#34;字串內容&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="c1">// 唯讀屬性
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def_property_readonly</span><span class="p">(</span><span class="s">&#34;length&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">                               <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">length</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">                               <span class="s">&#34;字串長度&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="p">.</span><span class="n">def_property_readonly</span><span class="p">(</span><span class="s">&#34;empty&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">                               <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">empty</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                               <span class="s">&#34;是否為空&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="p">.</span><span class="n">def_property_readonly</span><span class="p">(</span><span class="s">&#34;operation_count&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                               <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">operation_count</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">                               <span class="s">&#34;操作計數器&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="c1"></span>        <span class="c1">// 基本方法
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;reset_operation_count&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">reset_operation_count</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">             <span class="s">&#34;重置操作計數器&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="c1"></span>        <span class="c1">// 字串處理方法
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;to_upper&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">to_upper</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">             <span class="s">&#34;轉換為大寫&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;to_lower&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">to_lower</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">             <span class="s">&#34;轉換為小寫&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;reverse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">reverse</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">             <span class="s">&#34;反轉字串&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;trim&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">trim</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">             <span class="s">&#34;移除前後空白&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;split&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">split</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;delimiter&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34; &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">             <span class="s">&#34;以分隔符分割字串&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;replace&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">replace</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;old_str&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;new_str&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">             <span class="s">&#34;取代子字串&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="c1"></span>        <span class="c1">// 統計分析方法
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;char_frequency&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">char_frequency</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">             <span class="s">&#34;統計字元頻率&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;word_count&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">word_count</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">             <span class="s">&#34;計算單字數量&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;count_occurrences&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">count_occurrences</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;substring&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">             <span class="s">&#34;計算子字串出現次數&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="c1"></span>        <span class="c1">// 搜尋方法
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;find&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">find</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;substring&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;start&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">             <span class="s">&#34;搜尋子字串位置（找不到回傳 -1）&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;find_all&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">find_all</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;substring&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">             <span class="s">&#34;搜尋所有出現位置&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;contains&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">contains</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;substring&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">             <span class="s">&#34;是否包含子字串&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;starts_with&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">starts_with</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;prefix&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">             <span class="s">&#34;是否以指定字串開頭&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;ends_with&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">ends_with</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;suffix&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">             <span class="s">&#34;是否以指定字串結尾&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="c1"></span>        <span class="c1">// 運算子重載
</span></span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="c1">// + 運算子：StringProcessor + StringProcessor
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">+</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="c1">// += 運算子
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">+=</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="c1">// == 和 != 運算子
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">==</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">self</span> <span class="o">!=</span> <span class="n">py</span><span class="o">::</span><span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="c1">// [] 運算子：索引存取
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__getitem__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="k">operator</span><span class="p">[],</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;index&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">             <span class="s">&#34;取得指定位置的字元&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="c1"></span>        <span class="c1">// Python 特殊方法
</span></span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"></span>        <span class="c1">// ----------------------------------------
</span></span></span><span class="line"><span class="ln">142</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="c1">// __repr__：物件表示
</span></span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__repr__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">             <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">                 <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">repr</span> <span class="o">=</span> <span class="s">&#34;&lt;StringProcessor content=&#39;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">                 <span class="k">if</span> <span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="n">length</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">50</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">                     <span class="n">repr</span> <span class="o">+=</span> <span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">().</span><span class="n">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">47</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;...&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">                 <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">                     <span class="n">repr</span> <span class="o">+=</span> <span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">                 <span class="p">}</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">                 <span class="n">repr</span> <span class="o">+=</span> <span class="s">&#34;&#39; length=&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="n">length</span><span class="p">())</span> <span class="o">+</span> <span class="s">&#34;&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">                 <span class="k">return</span> <span class="n">repr</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">             <span class="p">})</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="c1">// __str__：字串轉換
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__str__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">             <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">                 <span class="k">return</span> <span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">             <span class="p">})</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">
</span></span><span class="line"><span class="ln">162</span><span class="cl">        <span class="c1">// __len__：長度
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__len__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">length</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="c1">// __bool__：布林轉換
</span></span></span><span class="line"><span class="ln">167</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__bool__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">             <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">                 <span class="k">return</span> <span class="o">!</span><span class="n">sp</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">             <span class="p">})</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">        <span class="c1">// __contains__：in 運算子
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__contains__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">             <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">contains</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;substring&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="c1">// __iter__：迭代支援
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__iter__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">             <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">                 <span class="k">return</span> <span class="n">py</span><span class="o">::</span><span class="n">make_iterator</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">().</span><span class="n">begin</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">                                          <span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">().</span><span class="n">end</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">             <span class="p">},</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">             <span class="n">py</span><span class="o">::</span><span class="n">keep_alive</span><span class="o">&lt;</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="o">&gt;</span><span class="p">())</span>  <span class="c1">// 保持物件存活
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="c1">// __hash__：雜湊支援（讓物件可作為 dict key）
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__hash__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">             <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                 <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">hash</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span><span class="p">{}(</span><span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">             <span class="p">})</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="c1">// 支援 pickle
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="c1"></span>        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="n">py</span><span class="o">::</span><span class="n">pickle</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">            <span class="c1">// __getstate__
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="c1"></span>            <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">                <span class="k">return</span> <span class="n">py</span><span class="o">::</span><span class="n">make_tuple</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">            <span class="c1">// __setstate__
</span></span></span><span class="line"><span class="ln">198</span><span class="cl"><span class="c1"></span>            <span class="p">[](</span><span class="n">py</span><span class="o">::</span><span class="n">tuple</span> <span class="n">t</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">                    <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="s">&#34;Invalid state&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">                <span class="k">return</span> <span class="nf">StringProcessor</span><span class="p">(</span><span class="n">t</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">cast</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="p">));</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="c1"></span>    <span class="c1">// 模組層級函式
</span></span></span><span class="line"><span class="ln">208</span><span class="cl"><span class="c1"></span>    <span class="c1">// ========================================
</span></span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="c1"></span>    <span class="n">m</span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;concatenate&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">          <span class="p">[](</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">StringProcessor</span><span class="o">&gt;&amp;</span> <span class="n">processors</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">             <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">separator</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">              <span class="k">if</span> <span class="p">(</span><span class="n">processors</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                  <span class="k">return</span> <span class="nf">StringProcessor</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">              <span class="p">}</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">              <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">result</span> <span class="o">=</span> <span class="n">processors</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">content</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">              <span class="k">for</span> <span class="p">(</span><span class="n">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">processors</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                  <span class="n">result</span> <span class="o">+=</span> <span class="n">separator</span> <span class="o">+</span> <span class="n">processors</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">content</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">              <span class="p">}</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">              <span class="k">return</span> <span class="nf">StringProcessor</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">          <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;processors&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">          <span class="n">py</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="s">&#34;separator&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">          <span class="s">&#34;串接多個 StringProcessor&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">    <span class="c1">// 版本資訊
</span></span></span><span class="line"><span class="ln">226</span><span class="cl"><span class="c1"></span>    <span class="n">m</span><span class="p">.</span><span class="n">attr</span><span class="p">(</span><span class="s">&#34;__version__&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;0.1.0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="步驟-5建構檔案">步驟 5：建構檔案</h3>
<p><strong>CMakeLists.txt</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmake" data-lang="cmake"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nb">cmake_minimum_required</span><span class="p">(</span><span class="s">VERSION</span> <span class="s">3.15</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="err"></span><span class="nb">project</span><span class="p">(</span><span class="s">string_processor</span> <span class="s">LANGUAGES</span> <span class="s">CXX</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="err"></span><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_CXX_STANDARD</span> <span class="s">17</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="err"></span><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_CXX_STANDARD_REQUIRED</span> <span class="s">ON</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="err"></span><span class="c"># 找到 pybind11
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c"></span><span class="nb">find_package</span><span class="p">(</span><span class="s">pybind11</span> <span class="s">REQUIRED</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="err"></span><span class="c"># 建立 Python 模組
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c"></span><span class="nb">pybind11_add_module</span><span class="p">(</span><span class="s">string_processor</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="s">src/string_processor.cpp</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s">src/bindings.cpp</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="err"></span><span class="c"># 包含標頭檔目錄
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c"></span><span class="nb">target_include_directories</span><span class="p">(</span><span class="s">string_processor</span> <span class="s">PRIVATE</span> <span class="s">src</span><span class="p">)</span><span class="err">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="err"></span><span class="c"># 優化設定
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c"></span><span class="nb">target_compile_options</span><span class="p">(</span><span class="s">string_processor</span> <span class="s">PRIVATE</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="o">$&lt;</span><span class="nv">$&lt;CXX_COMPILER_ID:GNU,Clang</span><span class="o">&gt;</span><span class="s">:-O3</span> <span class="s">-march=native&gt;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="o">$&lt;</span><span class="nv">$&lt;CXX_COMPILER_ID:MSVC</span><span class="o">&gt;</span><span class="s">:/O2&gt;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p><strong>setup.py</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># setup.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pybind11.setup_helpers</span> <span class="kn">import</span> <span class="n">Pybind11Extension</span><span class="p">,</span> <span class="n">build_ext</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">ext_modules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">Pybind11Extension</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="s2">&#34;string_processor&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">sources</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="s2">&#34;src/string_processor.cpp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="s2">&#34;src/bindings.cpp&#34;</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="n">include_dirs</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;src&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">cxx_std</span><span class="o">=</span><span class="mi">17</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">extra_compile_args</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;-O3&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">setup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">name</span><span class="o">=</span><span class="s2">&#34;string_processor&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">version</span><span class="o">=</span><span class="s2">&#34;0.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">description</span><span class="o">=</span><span class="s2">&#34;High-performance string processor using pybind11&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">ext_modules</span><span class="o">=</span><span class="n">ext_modules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">cmdclass</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;build_ext&#34;</span><span class="p">:</span> <span class="n">build_ext</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">python_requires</span><span class="o">=</span><span class="s2">&#34;&gt;=3.8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h2 id="記憶體管理與物件生命週期">記憶體管理與物件生命週期</h2>
<h3 id="pybind11-的記憶體管理策略">pybind11 的記憶體管理策略</h3>
<p>pybind11 提供多種方式控制物件的所有權：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 1. return_value_policy::automatic（預設）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// pybind11 自動決定最佳策略
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;get_content&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1">// 2. return_value_policy::copy
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// 總是建立副本，Python 擁有副本
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;get_content_copy&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">     <span class="n">py</span><span class="o">::</span><span class="n">return_value_policy</span><span class="o">::</span><span class="n">copy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 3. return_value_policy::reference
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// 回傳參考，不轉移所有權（危險：可能產生懸空指標）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;get_content_ref&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">     <span class="n">py</span><span class="o">::</span><span class="n">return_value_policy</span><span class="o">::</span><span class="n">reference</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// 4. return_value_policy::reference_internal
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">// 回傳參考，並保持父物件存活
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"></span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;get_content_internal&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">StringProcessor</span><span class="o">::</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">     <span class="n">py</span><span class="o">::</span><span class="n">return_value_policy</span><span class="o">::</span><span class="n">reference_internal</span><span class="p">)</span></span></span></code></pre></div><h3 id="keep_alive-策略">keep_alive 策略</h3>
<p>當物件間有依賴關係時，使用 <code>keep_alive</code> 確保生命週期正確：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// keep_alive&lt;Nurse, Patient&gt;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// Nurse: 需要被保持存活的物件的引數索引
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Patient: 依賴 Nurse 的物件的引數索引
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 0 = 回傳值, 1 = self, 2+ = 其他引數
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// 範例：迭代器需要保持原物件存活
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;__iter__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">     <span class="p">[](</span><span class="k">const</span> <span class="n">StringProcessor</span><span class="o">&amp;</span> <span class="n">sp</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">         <span class="k">return</span> <span class="n">py</span><span class="o">::</span><span class="n">make_iterator</span><span class="p">(</span><span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">().</span><span class="n">begin</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">                                  <span class="n">sp</span><span class="p">.</span><span class="n">content</span><span class="p">().</span><span class="n">end</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="n">py</span><span class="o">::</span><span class="n">keep_alive</span><span class="o">&lt;</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="o">&gt;</span><span class="p">())</span>  <span class="c1">// 回傳值(0)存活期間，self(1)必須存活
</span></span></span></code></pre></div><h3 id="智慧指標支援">智慧指標支援</h3>
<p>pybind11 自動支援 <code>std::shared_ptr</code> 和 <code>std::unique_ptr</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 使用 shared_ptr 管理物件
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="n">py</span><span class="o">::</span><span class="n">class_</span><span class="o">&lt;</span><span class="n">StringProcessor</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">StringProcessor</span><span class="o">&gt;&gt;</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="s">&#34;StringProcessor&#34;</span><span class="p">)</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="c1"></span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 工廠函式回傳 shared_ptr
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">m</span><span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">&#34;create_processor&#34;</span><span class="p">,</span> <span class="p">[]()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o">&lt;</span><span class="n">StringProcessor</span><span class="o">&gt;</span><span class="p">(</span><span class="s">&#34;factory created&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="python-使用範例">Python 使用範例</h2>
<h3 id="基本使用">基本使用</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">string_processor</span> <span class="kn">import</span> <span class="n">StringProcessor</span><span class="p">,</span> <span class="n">concatenate</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</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">sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;Hello, World!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span>           <span class="c1"># Hello, World!</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">repr</span><span class="p">(</span><span class="n">sp</span><span class="p">))</span>     <span class="c1"># &lt;StringProcessor content=&#39;Hello, World!&#39; length=13&gt;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sp</span><span class="p">))</span>      <span class="c1"># 13</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 屬性存取</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>   <span class="c1"># Hello, World!</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">length</span><span class="p">)</span>    <span class="c1"># 13</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">empty</span><span class="p">)</span>     <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 修改內容</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sp</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="s2">&#34;New content&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>   <span class="c1"># New content</span></span></span></code></pre></div><h3 id="字串處理">字串處理</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;  Hello, Python World!  &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</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="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">to_upper</span><span class="p">())</span>  <span class="c1"># &#34;  HELLO, PYTHON WORLD!  &#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">to_lower</span><span class="p">())</span>  <span class="c1"># &#34;  hello, python world!  &#34;</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"># 修剪空白</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">trim</span><span class="p">())</span>      <span class="c1"># &#34;Hello, Python World!&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 反轉</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">reverse</span><span class="p">())</span>   <span class="c1"># &#34;  !dlroW nohtyP ,olleH  &#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 分割</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;apple,banana,cherry&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;,&#34;</span><span class="p">))</span>  <span class="c1"># [&#39;apple&#39;, &#39;banana&#39;, &#39;cherry&#39;]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 取代</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;,&#34;</span><span class="p">,</span> <span class="s2">&#34; | &#34;</span><span class="p">))</span>  <span class="c1"># &#34;apple | banana | cherry&#34;</span></span></span></code></pre></div><h3 id="統計與搜尋">統計與搜尋</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;hello hello world&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</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="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">word_count</span><span class="p">())</span>              <span class="c1"># 3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">count_occurrences</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">))</span> <span class="c1"># 2</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">char_frequency</span><span class="p">())</span>          <span class="c1"># {&#39;h&#39;: 2, &#39;e&#39;: 2, &#39;l&#39;: 5, ...}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 搜尋</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s2">&#34;world&#34;</span><span class="p">))</span>             <span class="c1"># 12</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">))</span>         <span class="c1"># [0, 6]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">contains</span><span class="p">(</span><span class="s2">&#34;world&#34;</span><span class="p">))</span>         <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">starts_with</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">))</span>      <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp</span><span class="o">.</span><span class="n">ends_with</span><span class="p">(</span><span class="s2">&#34;world&#34;</span><span class="p">))</span>        <span class="c1"># True</span></span></span></code></pre></div><h3 id="運算子使用">運算子使用</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">sp1</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;Hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">sp2</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34; World&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># + 運算子</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">sp3</span> <span class="o">=</span> <span class="n">sp1</span> <span class="o">+</span> <span class="n">sp2</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp3</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>   <span class="c1"># &#34;Hello World&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># += 運算子</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">sp1</span> <span class="o">+=</span> <span class="n">sp2</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp1</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>   <span class="c1"># &#34;Hello World&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># [] 索引</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp3</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>        <span class="c1"># &#39;H&#39;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp3</span><span class="p">[</span><span class="mi">6</span><span class="p">])</span>        <span class="c1"># &#39;W&#39;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># in 運算子</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;World&#34;</span> <span class="ow">in</span> <span class="n">sp3</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 比較運算子</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp1</span> <span class="o">==</span> <span class="n">sp3</span><span class="p">)</span>    <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp1</span> <span class="o">!=</span> <span class="n">sp2</span><span class="p">)</span>    <span class="c1"># True</span></span></span></code></pre></div><h3 id="python-特殊功能">Python 特殊功能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">copy</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pickle</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="s2">&#34;test data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 迭代</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">sp</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>  <span class="c1"># t e s t   d a t a</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 複製</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">sp_copy</span> <span class="o">=</span> <span class="n">copy</span><span class="o">.</span><span class="n">copy</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 序列化</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">sp</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sp_restored</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sp_restored</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>  <span class="c1"># &#34;test data&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 作為 dict key</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">cache</span> <span class="o">=</span> <span class="p">{</span><span class="n">sp</span><span class="p">:</span> <span class="s2">&#34;cached value&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">cache</span><span class="p">[</span><span class="n">sp</span><span class="p">])</span>  <span class="c1"># &#34;cached value&#34;</span></span></span></code></pre></div><h2 id="效能測試">效能測試</h2>
<p>建立效能測試腳本，比較 C++ 綁定與純 Python 實作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1"># benchmark.py</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">效能比較：pybind11 StringProcessor vs 純 Python
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="kn">import</span> <span class="nn">statistics</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="c1"># pybind11 版本</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">string_processor</span> <span class="kn">import</span> <span class="n">StringProcessor</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1"># 純 Python 版本</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">class</span> <span class="nc">PyStringProcessor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;純 Python 實作作為效能基準&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_content</span> <span class="o">=</span> <span class="n">content</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">content</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="nd">@content.setter</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="k">def</span> <span class="nf">content</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_content</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="k">def</span> <span class="nf">to_upper</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="k">def</span> <span class="nf">to_lower</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">reverse</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">def</span> <span class="nf">split</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">delimiter</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">delimiter</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="k">def</span> <span class="nf">char_frequency</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="n">freq</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">            <span class="n">freq</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="n">freq</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="k">return</span> <span class="n">freq</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">def</span> <span class="nf">word_count</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="k">def</span> <span class="nf">count_occurrences</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">substring</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">,</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">if</span> <span class="n">pos</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="n">start</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">substring</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">find_all</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">substring</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">positions</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">pos</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_content</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">substring</span><span class="p">,</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="k">if</span> <span class="n">pos</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">            <span class="n">positions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="n">start</span> <span class="o">=</span> <span class="n">pos</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">substring</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">return</span> <span class="n">positions</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">Any</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">              <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">              <span class="n">warmup</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行效能測試並回傳統計資料&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="c1"># 預熱</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">warmup</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="c1"># 正式測試</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="n">func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span>  <span class="c1"># 轉換為毫秒</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="s2">&#34;mean&#34;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;stdev&#34;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">stdev</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="s2">&#34;min&#34;</span><span class="p">:</span> <span class="nb">min</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="s2">&#34;max&#34;</span><span class="p">:</span> <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="s2">&#34;median&#34;</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">median</span><span class="p">(</span><span class="n">times</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="k">def</span> <span class="nf">generate_test_content</span><span class="p">(</span><span class="n">size</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="s2">&#34;&#34;&#34;產生測試用字串&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">base</span> <span class="o">=</span> <span class="s2">&#34;Hello World! This is a test string for benchmarking. &#34;</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">base</span> <span class="o">*</span> <span class="p">(</span><span class="n">size</span> <span class="o">//</span> <span class="nb">len</span><span class="p">(</span><span class="n">base</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))[:</span><span class="n">size</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="k">def</span> <span class="nf">run_benchmarks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行所有效能測試&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;StringProcessor 效能測試：pybind11 vs 純 Python&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="n">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1_000</span><span class="p">,</span> <span class="mi">10_000</span><span class="p">,</span> <span class="mi">100_000</span><span class="p">,</span> <span class="mi">1_000_000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">sizes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="n">size</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">cpp_sp</span> <span class="o">=</span> <span class="n">StringProcessor</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">py_sp</span> <span class="o">=</span> <span class="n">PyStringProcessor</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">--- 字串長度：</span><span class="si">{</span><span class="n">size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> 字元 ---</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="c1"># 測試項目</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="n">tests</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;to_upper&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">to_upper</span><span class="p">(),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">to_upper</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;to_lower&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">to_lower</span><span class="p">(),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">to_lower</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;reverse&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">reverse</span><span class="p">(),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">reverse</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;char_frequency&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">char_frequency</span><span class="p">(),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">char_frequency</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;word_count&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">word_count</span><span class="p">(),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">word_count</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;count_occurrences&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">count_occurrences</span><span class="p">(</span><span class="s2">&#34;test&#34;</span><span class="p">),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">count_occurrences</span><span class="p">(</span><span class="s2">&#34;test&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="p">(</span><span class="s2">&#34;find_all&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">cpp_sp</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s2">&#34;Hello&#34;</span><span class="p">),</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">py_sp</span><span class="o">.</span><span class="n">find_all</span><span class="p">(</span><span class="s2">&#34;Hello&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">cpp_func</span><span class="p">,</span> <span class="n">py_func</span> <span class="ow">in</span> <span class="n">tests</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="n">cpp_result</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">cpp_func</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">            <span class="n">py_result</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">py_func</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">            <span class="n">speedup</span> <span class="o">=</span> <span class="n">py_result</span><span class="p">[</span><span class="s2">&#34;mean&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">cpp_result</span><span class="p">[</span><span class="s2">&#34;mean&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">:</span><span class="s2">20s</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  C++:    </span><span class="si">{</span><span class="n">cpp_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">8.4f</span><span class="si">}</span><span class="s2"> ms (stdev: </span><span class="si">{</span><span class="n">cpp_result</span><span class="p">[</span><span class="s1">&#39;stdev&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Python: </span><span class="si">{</span><span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;mean&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">8.4f</span><span class="si">}</span><span class="s2"> ms (stdev: </span><span class="si">{</span><span class="n">py_result</span><span class="p">[</span><span class="s1">&#39;stdev&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">70</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="n">run_benchmarks</span><span class="p">()</span></span></span></code></pre></div><h3 id="預期效能結果">預期效能結果</h3>





<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">======================================================================
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">StringProcessor 效能測試：pybind11 vs 純 Python
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">======================================================================
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">--- 字串長度：1,000 字元 ---
</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">to_upper
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  C++:      0.0012 ms (stdev: 0.0003)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  Python:   0.0008 ms (stdev: 0.0002)
</span></span><span class="line"><span class="ln">10</span><span class="cl">  加速比: 0.67x
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">char_frequency
</span></span><span class="line"><span class="ln">13</span><span class="cl">  C++:      0.0089 ms (stdev: 0.0012)
</span></span><span class="line"><span class="ln">14</span><span class="cl">  Python:   0.0423 ms (stdev: 0.0045)
</span></span><span class="line"><span class="ln">15</span><span class="cl">  加速比: 4.75x
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">word_count
</span></span><span class="line"><span class="ln">18</span><span class="cl">  C++:      0.0034 ms (stdev: 0.0008)
</span></span><span class="line"><span class="ln">19</span><span class="cl">  Python:   0.0028 ms (stdev: 0.0006)
</span></span><span class="line"><span class="ln">20</span><span class="cl">  加速比: 0.82x
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">--- 字串長度：100,000 字元 ---
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">to_upper
</span></span><span class="line"><span class="ln">25</span><span class="cl">  C++:      0.0892 ms (stdev: 0.0089)
</span></span><span class="line"><span class="ln">26</span><span class="cl">  Python:   0.0634 ms (stdev: 0.0067)
</span></span><span class="line"><span class="ln">27</span><span class="cl">  加速比: 0.71x
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">char_frequency
</span></span><span class="line"><span class="ln">30</span><span class="cl">  C++:      0.7823 ms (stdev: 0.0456)
</span></span><span class="line"><span class="ln">31</span><span class="cl">  Python:   4.2341 ms (stdev: 0.2134)
</span></span><span class="line"><span class="ln">32</span><span class="cl">  加速比: 5.41x
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">count_occurrences
</span></span><span class="line"><span class="ln">35</span><span class="cl">  C++:      0.0234 ms (stdev: 0.0034)
</span></span><span class="line"><span class="ln">36</span><span class="cl">  Python:   0.0567 ms (stdev: 0.0078)
</span></span><span class="line"><span class="ln">37</span><span class="cl">  加速比: 2.42x
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">find_all
</span></span><span class="line"><span class="ln">40</span><span class="cl">  C++:      0.0312 ms (stdev: 0.0045)
</span></span><span class="line"><span class="ln">41</span><span class="cl">  Python:   0.0823 ms (stdev: 0.0098)
</span></span><span class="line"><span class="ln">42</span><span class="cl">  加速比: 2.64x
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">======================================================================</span></span></code></pre></div><h3 id="效能分析">效能分析</h3>
<table>
  <thead>
      <tr>
          <th>操作類型</th>
          <th>C++ 優勢</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>字元頻率統計</strong></td>
          <td>4-6x</td>
          <td>C++ unordered_map 比 Python dict 更快</td>
      </tr>
      <tr>
          <td><strong>搜尋操作</strong></td>
          <td>2-3x</td>
          <td>C++ string::find 效率高</td>
      </tr>
      <tr>
          <td><strong>大小寫轉換</strong></td>
          <td>0.7x</td>
          <td>Python 內建函式已高度優化</td>
      </tr>
      <tr>
          <td><strong>單字計數</strong></td>
          <td>0.8-1x</td>
          <td>Python split() 非常高效</td>
      </tr>
  </tbody>
</table>
<p><strong>重點觀察</strong>：</p>
<ol>
<li><strong>不是所有操作都能加速</strong>：Python 的內建字串方法（如 <code>upper()</code>、<code>split()</code>）已經用 C 實作，pybind11 包裝反而增加呼叫開銷</li>
<li><strong>複雜操作效益明顯</strong>：需要多次迴圈或資料結構操作的方法（如字元頻率統計）獲益最大</li>
<li><strong>資料量影響顯著</strong>：資料量越大，C++ 的優勢越明顯</li>
</ol>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>純 Python</th>
          <th>pybind11 C++ 綁定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>開發速度</strong></td>
          <td>快</td>
          <td>中（需要 C++ 開發經驗）</td>
      </tr>
      <tr>
          <td><strong>效能</strong></td>
          <td>基準</td>
          <td>特定操作 2-6x 加速</td>
      </tr>
      <tr>
          <td><strong>記憶體使用</strong></td>
          <td>較高</td>
          <td>較低（C++ 記憶體管理）</td>
      </tr>
      <tr>
          <td><strong>除錯難度</strong></td>
          <td>低</td>
          <td>中高（需要 C++ 除錯工具）</td>
      </tr>
      <tr>
          <td><strong>部署複雜度</strong></td>
          <td>簡單</td>
          <td>需要編譯環境</td>
      </tr>
      <tr>
          <td><strong>可維護性</strong></td>
          <td>高</td>
          <td>中（需要維護兩種語言）</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用-pybind11-綁定-c-類別">何時使用 pybind11 綁定 C++ 類別？</h3>
<p><strong>適合使用</strong>：</p>
<ul>
<li>已有成熟的 C++ 程式庫需要在 Python 中使用</li>
<li>需要精細的記憶體管理</li>
<li>效能瓶頸在資料結構操作而非 I/O</li>
<li>需要與其他 C++ 系統整合</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>純字串處理（Python 內建已很快）</li>
<li>簡單的資料容器（用 Python dataclass 更簡潔）</li>
<li>快速原型開發</li>
<li>團隊沒有 C++ 經驗</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<p>擴展 StringProcessor，新增以下方法：</p>
<ol>
<li><code>join(separator: str, strings: list[str])</code> - 用分隔符串接字串列表</li>
<li><code>pad_left(width: int, char: str)</code> - 左側填充字元</li>
<li><code>pad_right(width: int, char: str)</code> - 右側填充字元</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<p>建立一個 <code>DataBuffer</code> 類別，展示：</p>
<ol>
<li>使用 <code>std::vector&lt;uint8_t&gt;</code> 儲存二進位資料</li>
<li>支援 Python buffer protocol（可與 NumPy 互通）</li>
<li>實作切片操作（<code>__getitem__</code> 支援 slice）</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<p>比較三種綁定方式的效能：</p>
<ol>
<li>pybind11 直接綁定</li>
<li>pybind11 + 釋放 GIL</li>
<li>使用 NumPy 陣列避免資料複製</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://pybind11.readthedocs.io/en/stable/classes.html">pybind11 官方文件：類別</a></li>
<li><a href="https://pybind11.readthedocs.io/en/stable/operators.html">pybind11 官方文件：運算子重載</a></li>
<li><a href="https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html">pybind11 官方文件：智慧指標</a></li>
<li><a href="https://pybind11.readthedocs.io/en/stable/advanced/functions.html#return-value-policies">pybind11 記憶體管理最佳實踐</a></li>
</ul>
<hr>
<p><em>返回：<a href="/blog/python-advanced/05-c-extensions/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的 C 擴展案例">案例研究</a></em>
<em>返回：<a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">模組五：用 C 擴展 Python</a></em></p>
]]></content:encoded></item><item><title>案例：正則表達式預編譯</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何透過正則表達式預編譯來減少重複編譯的開銷。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八基礎章節&lt;/a>&lt;/li>
&lt;li>Python &lt;code>re&lt;/code> 模組基本操作&lt;/li>
&lt;li>基本的效能測量概念&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 是一個 Hook 合規性驗證工具，用於檢查 Hook 腳本是否遵循專案規範。它定義了多組正則表達式模式來偵測各種程式碼特徵：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 共用模組導入模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &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 class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_LOGGING_PATTERNS&lt;/span> &lt;span class="o">=&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_logging\s+import&amp;#34;&lt;/span>&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">CONFIG_LOADER_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">GIT_UTILS_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 輸出函式使用模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;write_hook_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_pretooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_posttooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 不推薦的輸出模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">BAD_OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;print\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 命名規範模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">VALID_NAME_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Hook 類型推測模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_TYPE_HINTS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_pretooluse_output|permissionDecision&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_posttooluse_output|additionalContext&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;Stop|subagent&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;SessionStart|session_id&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&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>re.search()&lt;/code> 進行匹配：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_has_import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&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="s2">&amp;#34;&amp;#34;&amp;#34;檢查是否有符合任一模式的導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&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">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&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="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_matches_pattern&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&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="s2">&amp;#34;&amp;#34;&amp;#34;檢查是否符合任一模式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&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="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&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="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="python-re-的內部快取">Python re 的內部快取&lt;/h3>
&lt;p>在討論優化之前，我們需要了解 Python &lt;code>re&lt;/code> 模組的內部機制。&lt;/p>
&lt;p>當你使用 &lt;code>re.search(pattern, string)&lt;/code> 這樣的函式時，Python 會在內部執行兩個步驟：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>編譯&lt;/strong>：將正則表達式字串轉換為內部的 pattern 物件&lt;/li>
&lt;li>&lt;strong>匹配&lt;/strong>：使用 pattern 物件對目標字串進行匹配&lt;/li>
&lt;/ol>
&lt;p>為了避免重複編譯，&lt;code>re&lt;/code> 模組內建了一個 LRU 快取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 內部實作概念（簡化版）&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">_cache&lt;/span> &lt;span class="o">=&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">_MAXCACHE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">512&lt;/span> &lt;span class="c1"># Python 3.12 的預設值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&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 class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&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"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 實際編譯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sre_compile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 儲存到快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="n">_MAXCACHE&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&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">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">compiled&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">compiled&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可以驗證這個快取的存在：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;快取大小上限: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_MAXCACHE&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 第一次呼叫會編譯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\d+&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;test123&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查看快取內容（僅供觀察，不建議在生產環境使用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;目前快取數量: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼還需要手動預編譯">為什麼還需要手動預編譯？&lt;/h3>
&lt;p>既然 &lt;code>re&lt;/code> 有內建快取，為什麼還需要手動使用 &lt;code>re.compile()&lt;/code>？原因有幾個：&lt;/p>
&lt;h4 id="1-快取查找有開銷">1. 快取查找有開銷&lt;/h4>
&lt;p>每次使用 &lt;code>re.search()&lt;/code> 時，都需要：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 虛擬碼：每次 re.search() 的內部流程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&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="c1"># 1. 建立快取鍵（需要計算 hash）&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">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 2. 查找快取（dict lookup）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&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="k">else&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">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_compile_and_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 3. 執行匹配&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">compiled&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>相比之下，預編譯後直接使用：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何透過正則表達式預編譯來減少重複編譯的開銷。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八基礎章節</a></li>
<li>Python <code>re</code> 模組基本操作</li>
<li>基本的效能測量概念</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 是一個 Hook 合規性驗證工具，用於檢查 Hook 腳本是否遵循專案規範。它定義了多組正則表達式模式來偵測各種程式碼特徵：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 共用模組導入模式</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 輸出函式使用模式</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 不推薦的輸出模式</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># 命名規範模式</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="c1"># Hook 類型推測模式</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="p">]</span></span></span></code></pre></div><p>目前的實作將模式儲存為字串列表，並在輔助方法中使用 <code>re.search()</code> 進行匹配：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><h3 id="python-re-的內部快取">Python re 的內部快取</h3>
<p>在討論優化之前，我們需要了解 Python <code>re</code> 模組的內部機制。</p>
<p>當你使用 <code>re.search(pattern, string)</code> 這樣的函式時，Python 會在內部執行兩個步驟：</p>
<ol>
<li><strong>編譯</strong>：將正則表達式字串轉換為內部的 pattern 物件</li>
<li><strong>匹配</strong>：使用 pattern 物件對目標字串進行匹配</li>
</ol>
<p>為了避免重複編譯，<code>re</code> 模組內建了一個 LRU 快取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Python 內部實作概念（簡化版）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_cache</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_MAXCACHE</span> <span class="o">=</span> <span class="mi">512</span>  <span class="c1"># Python 3.12 的預設值</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">_compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>  <span class="c1"># 快取命中</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 實際編譯</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">compiled</span> <span class="o">=</span> <span class="n">sre_compile</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 儲存到快取</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">_MAXCACHE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>  <span class="c1"># 快取滿了就清空</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">compiled</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">compiled</span></span></span></code></pre></div><p>你可以驗證這個快取的存在：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取大小上限: </span><span class="si">{</span><span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 第一次呼叫會編譯</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\d+&#39;</span><span class="p">,</span> <span class="s1">&#39;test123&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 查看快取內容（僅供觀察，不建議在生產環境使用）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目前快取數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="為什麼還需要手動預編譯">為什麼還需要手動預編譯？</h3>
<p>既然 <code>re</code> 有內建快取，為什麼還需要手動使用 <code>re.compile()</code>？原因有幾個：</p>
<h4 id="1-快取查找有開銷">1. 快取查找有開銷</h4>
<p>每次使用 <code>re.search()</code> 時，都需要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 虛擬碼：每次 re.search() 的內部流程</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 1. 建立快取鍵（需要計算 hash）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 2. 查找快取（dict lookup）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">compiled</span> <span class="o">=</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">compiled</span> <span class="o">=</span> <span class="n">_compile_and_cache</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 3. 執行匹配</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">compiled</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">string</span><span class="p">)</span></span></span></code></pre></div><p>相比之下，預編譯後直接使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 預編譯</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\d+&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 直接使用，無需快取查找</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">string</span><span class="p">)</span></span></span></code></pre></div><h4 id="2-快取可能被清空">2. 快取可能被清空</h4>
<p>當快取達到上限（預設 512 個模式）時，整個快取會被清空：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">_MAXCACHE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>  <span class="c1"># 全部清空！</span></span></span></code></pre></div><p>這表示在大型專案中，你的常用模式可能會被意外從快取中移除。</p>
<h4 id="3-語意更清晰">3. 語意更清晰</h4>
<p>預編譯讓程式碼意圖更明確：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不清楚：pattern 是什麼時候編譯的？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern1&#39;</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern2&#39;</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 清楚：模式在類別載入時就編譯好了</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">PATTERN1</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern1&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">PATTERN2</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern2&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">PATTERN1</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="o">...</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">PATTERN2</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="o">...</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別需要預編譯的模式">步驟 1：識別需要預編譯的模式</h4>
<p>首先，找出所有會被重複使用的正則表達式。在 <code>hook_validator.py</code> 中，以下模式會在每次驗證時使用：</p>
<ul>
<li>導入檢查模式（7 組，共 14 個模式）</li>
<li>輸出格式檢查模式（5 個模式）</li>
<li>命名規範模式（1 個模式）</li>
<li>Hook 類型推測模式（4 個模式）</li>
</ul>
<h4 id="步驟-2建立預編譯版本">步驟 2：建立預編譯版本</h4>
<p>將字串模式轉換為已編譯的 pattern 物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidatorOptimized</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用預編譯正則表達式的 Hook 驗證器&#34;&#34;&#34;</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"># 預編譯的導入模式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</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></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># 預編譯的輸出模式</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="c1"># 預編譯的命名模式</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="c1"># 預編譯的 Hook 類型推測模式</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="c1"># 其他預編譯模式</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">JSON_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;json\.dumps&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_.*_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="p">]</span></span></span></code></pre></div><h4 id="步驟-3更新匹配方法">步驟 3：更新匹配方法</h4>
<p>修改輔助方法，使用預編譯的 pattern 物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入（使用預編譯模式）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>  <span class="c1"># 直接使用 Pattern.search()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式（使用預編譯模式）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">_has_json_output</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有 JSON 輸出相關程式碼&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">JSON_OUTPUT_PATTERNS</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<p>以下是完整的優化版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Hook 合規性驗證工具（優化版）
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">使用 re.compile 預編譯所有正則表達式，減少重複編譯開銷。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidatorOptimized</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    使用預編譯正則表達式的 Hook 驗證器
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    所有正則表達式在類別定義時編譯一次，
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">    之後所有實例共享這些已編譯的 pattern 物件。
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="c1"># ===== 預編譯的正則表達式 =====</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="c1"># 導入模式</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1"># 輸出模式</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="c1"># 命名模式</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="n">VALID_NAME_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="c1"># Hook 類型推測</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="c1"># JSON 輸出檢測</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">JSON_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;json\.dumps&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_.*_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="c1"># 功能推測模式</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">CONFIG_KEYWORDS_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;load_config|configuration|config|yaml|json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="n">GIT_KEYWORDS_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;git|branch|commit|worktree|is_protected_branch|get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;初始化驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_json_output</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有 JSON 輸出相關程式碼&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">JSON_OUTPUT_PATTERNS</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="k">def</span> <span class="nf">_needs_config_loader</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷 Hook 是否需要配置載入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">CONFIG_KEYWORDS_PATTERN</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="k">if</span> <span class="n">hook_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="n">name_lower</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">name_lower</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;agent&#34;</span><span class="p">,</span> <span class="s2">&#34;dispatch&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="k">def</span> <span class="nf">_needs_git_utils</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷 Hook 是否需要 Git 操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">GIT_KEYWORDS_PATTERN</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="k">if</span> <span class="n">hook_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">            <span class="n">name_lower</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">name_lower</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="s2">&#34;commit&#34;</span><span class="p">,</span> <span class="s2">&#34;worktree&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERN</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;建議使用 snake-case 或 kebab-case 命名&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">def</span> <span class="nf">infer_hook_type</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據內容推測 Hook 類型&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">for</span> <span class="n">hook_type</span><span class="p">,</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="k">if</span> <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">                <span class="k">return</span> <span class="n">hook_type</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">path</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="c1"># 使用預編譯模式進行各項檢查</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_has_import</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;未導入 hook_io 模組&#34;</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">
</span></span><span class="line"><span class="ln">212</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_matches_pattern</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;使用不推薦的輸出方式&#34;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">
</span></span><span class="line"><span class="ln">218</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<p>使用 <code>timeit</code> 模組來精確測量預編譯帶來的效能提升：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">正則表達式預編譯效能測試
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">比較：
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">1. 每次使用 re.search(string_pattern, content)
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">2. 使用預編譯的 pattern.search(content)
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Pattern</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1"># 測試用的正則表達式模式（來自 hook_validator.py）</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="n">STRING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"># 預編譯版本</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="n">COMPILED_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">STRING_PATTERNS</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="c1"># 模擬的 Hook 檔案內容</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="n">SAMPLE_CONTENT</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s1">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s1">&#34;&#34;&#34;Sample hook for testing&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s1">import json
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s1">import sys
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s1">from pathlib import Path
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s1">from hook_logging import setup_hook_logging
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s1">from config_loader import load_config
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s1">logger = setup_hook_logging(__name__)
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s1">    &#34;&#34;&#34;Main entry point&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s1">    input_data = read_hook_input()
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s1">    config = load_config()
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s1">    # Process the input
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s1">    result = process(input_data, config)
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s1">    # Write output using recommended function
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s1">    write_hook_output({
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s1">        &#34;result&#34;: &#34;continue&#34;,
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s1">        &#34;additionalContext&#34;: result
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s1">    })
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s1">def process(data, config):
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s1">    &#34;&#34;&#34;Process the hook data&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s1">    return {&#34;status&#34;: &#34;ok&#34;}
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="k">def</span> <span class="nf">search_with_strings</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用字串模式搜尋（每次都會經過 re 快取）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="k">def</span> <span class="nf">search_with_compiled</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用預編譯模式搜尋&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="nb">bool</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行效能測試&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="c1"># 預熱（讓 re 模組的快取填滿）</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="c1"># 測試參數</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">10000</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">repeat</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="c1"># 測試字串模式（有 re 快取）</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="n">string_times</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">repeat</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">iterations</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="n">repeat</span><span class="o">=</span><span class="n">repeat</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="c1"># 測試預編譯模式</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">compiled_times</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">repeat</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">iterations</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">repeat</span><span class="o">=</span><span class="n">repeat</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="c1"># 計算結果</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">string_best</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">string_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="n">compiled_best</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">compiled_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">string_best</span> <span class="o">/</span> <span class="n">compiled_best</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="c1"># 輸出結果</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;正則表達式預編譯效能測試&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試內容大小: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">)</span><span class="si">}</span><span class="s2"> 字元&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;模式數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">STRING_PATTERNS</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;迭代次數: </span><span class="si">{</span><span class="n">iterations</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> 次 x </span><span class="si">{</span><span class="n">repeat</span><span class="si">}</span><span class="s2"> 輪&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;結果（最佳時間）:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式 (re.search):     </span><span class="si">{</span><span class="n">string_best</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式 (Pattern):     </span><span class="si">{</span><span class="n">compiled_best</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:                   </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="c1"># 單次操作時間</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">string_per_op</span> <span class="o">=</span> <span class="p">(</span><span class="n">string_best</span> <span class="o">/</span> <span class="n">iterations</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span>  <span class="c1"># 微秒</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="n">compiled_per_op</span> <span class="o">=</span> <span class="p">(</span><span class="n">compiled_best</span> <span class="o">/</span> <span class="n">iterations</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;單次操作時間:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式:                 </span><span class="si">{</span><span class="n">string_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式:               </span><span class="si">{</span><span class="n">compiled_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;每次節省:                 </span><span class="si">{</span><span class="n">string_per_op</span> <span class="o">-</span> <span class="n">compiled_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="s2">&#34;string_time&#34;</span><span class="p">:</span> <span class="n">string_best</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="s2">&#34;compiled_time&#34;</span><span class="p">:</span> <span class="n">compiled_best</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">speedup</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_cache_miss</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試快取未命中的情況&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;快取未命中測試（清空快取後）&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="c1"># 清空 re 模組快取</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="c1"># 測試字串模式（快取被清空）</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>  <span class="c1"># 每次都清空快取</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">string_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="c1"># 測試預編譯模式（不受快取影響）</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>  <span class="c1"># 清空快取不影響預編譯模式</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">string_time</span> <span class="o">/</span> <span class="n">compiled_time</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式 (無快取):        </span><span class="si">{</span><span class="n">string_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式:               </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:                   </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">
</span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="n">benchmark_cache_miss</span><span class="p">()</span></span></span></code></pre></div><h3 id="典型測試結果">典型測試結果</h3>





<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">正則表達式預編譯效能測試
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">測試內容大小: 847 字元
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">模式數量: 14 個
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">迭代次數: 10,000 次 x 5 輪
</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></span><span class="line"><span class="ln"> 8</span><span class="cl">------------------------------------------------------------
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">字串模式 (re.search):     0.4823 秒
</span></span><span class="line"><span class="ln">10</span><span class="cl">預編譯模式 (Pattern):     0.3891 秒
</span></span><span class="line"><span class="ln">11</span><span class="cl">加速比:                   1.24x
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">單次操作時間:
</span></span><span class="line"><span class="ln">14</span><span class="cl">------------------------------------------------------------
</span></span><span class="line"><span class="ln">15</span><span class="cl">字串模式:                 48.23 微秒
</span></span><span class="line"><span class="ln">16</span><span class="cl">預編譯模式:               38.91 微秒
</span></span><span class="line"><span class="ln">17</span><span class="cl">每次節省:                 9.32 微秒
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">20</span><span class="cl">快取未命中測試（清空快取後）
</span></span><span class="line"><span class="ln">21</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">22</span><span class="cl">字串模式 (無快取):        2.3456 秒
</span></span><span class="line"><span class="ln">23</span><span class="cl">預編譯模式:               0.3912 秒
</span></span><span class="line"><span class="ln">24</span><span class="cl">加速比:                   6.00x</span></span></code></pre></div><p>從結果可以看出：</p>
<ul>
<li><strong>正常情況</strong>：預編譯帶來約 <strong>1.2-1.3 倍</strong> 的加速</li>
<li><strong>快取未命中</strong>：當 <code>re</code> 模組快取失效時，加速可達 <strong>6 倍</strong></li>
</ul>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>字串模式</th>
          <th>預編譯模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體使用</td>
          <td>較低（依賴 re 快取）</td>
          <td>略高（每個 Pattern 物件）</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><code>List[str]</code></td>
          <td><code>List[Pattern]</code></td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>少量模式、低頻呼叫</td>
          <td>多模式、高頻呼叫</td>
      </tr>
  </tbody>
</table>
<h3 id="記憶體考量">記憶體考量</h3>
<p>預編譯的 Pattern 物件會佔用額外記憶體：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">pattern_str</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">pattern_obj</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern_str</span><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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串大小: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Pattern 大小: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">pattern_obj</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 字串大小: 74 bytes</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># Pattern 大小: 256 bytes（視模式複雜度而定）</span></span></span></code></pre></div><p>但在大多數情況下，這點記憶體是值得的。</p>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合預編譯的情況">適合預編譯的情況</h3>
<ul>
<li>同一個模式會被使用多次（例如在迴圈中）</li>
<li>模式數量較多，可能超過 <code>re</code> 快取上限（512 個）</li>
<li>效能敏感的程式碼路徑</li>
<li>需要穩定、可預測的執行時間</li>
<li>類別或模組級別的模式定義</li>
</ul>
<h3 id="不需要預編譯的情況">不需要預編譯的情況</h3>
<ul>
<li>模式只使用一次</li>
<li>快速原型開發</li>
<li>簡單的腳本工具</li>
<li>模式是動態生成的</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習測量你的正則表達式效能">基礎練習：測量你的正則表達式效能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 1：測量自己專案中正則表達式的效能
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">步驟：
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">1. 找出你專案中使用正則表達式的程式碼
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">2. 記錄有多少個不同的模式
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">3. 測量預編譯前後的效能差異
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># TODO: 將你專案中的模式填入這裡</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">YOUR_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;your_pattern_1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;your_pattern_2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">YOUR_TEST_CONTENT</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">your test content here
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">def</span> <span class="nf">measure_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量效能差異&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 字串版本</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">string_patterns</span> <span class="o">=</span> <span class="n">YOUR_PATTERNS</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># 預編譯版本</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">compiled_patterns</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">YOUR_PATTERNS</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 測量</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">string_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">YOUR_TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">string_patterns</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="mi">10000</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">YOUR_TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">compiled_patterns</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="mi">10000</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式: </span><span class="si">{</span><span class="n">string_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式: </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">string_time</span> <span class="o">/</span> <span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">measure_performance</span><span class="p">()</span></span></span></code></pre></div><h3 id="進階練習監控-re-快取狀態">進階練習：監控 re 快取狀態</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 2：監控 re 模組的快取狀態
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">了解你的程式實際使用了多少快取空間。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">&#34;&#34;&#34;</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="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">check_cache_status</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查 re 模組快取狀態&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取上限: </span><span class="si">{</span><span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目前快取數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;使用率: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">/</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span> <span class="o">*</span> <span class="mf">0.8</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;警告：快取即將滿載！&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">simulate_cache_overflow</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬快取溢出&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;模擬快取溢出...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 記錄初始狀態</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">initial_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 建立大量不同的模式</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">600</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;pattern_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;test content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">final_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;初始快取: </span><span class="si">{</span><span class="n">initial_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;最終快取: </span><span class="si">{</span><span class="n">final_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取被清空了 </span><span class="si">{</span><span class="p">(</span><span class="mi">600</span> <span class="o">-</span> <span class="n">final_count</span><span class="p">)</span> <span class="o">//</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2"> 次&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">check_cache_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">simulate_cache_overflow</span><span class="p">()</span></span></span></code></pre></div><h3 id="挑戰題建立自動預編譯裝飾器">挑戰題：建立自動預編譯裝飾器</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 3：建立自動預編譯裝飾器
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">設計一個裝飾器，自動將類別中的字串模式轉換為預編譯模式。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">&#34;&#34;&#34;</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="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">Type</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">auto_compile_patterns</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    類別裝飾器：自動預編譯所有 _PATTERNS 結尾的類別屬性
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        @auto_compile_patterns
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        class MyValidator:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            IMPORT_PATTERNS = [
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">                r&#34;from\s+module\s+import&#34;,
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">                r&#34;import\s+module&#34;,
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            ]
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">attr_name</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="n">attr_name</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&#34;_PATTERNS&#34;</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">attr_name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">list</span><span class="p">)</span> <span class="ow">and</span> <span class="n">value</span> <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                <span class="c1"># 將字串列表轉換為預編譯模式列表</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">compiled</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="nb">setattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">compiled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯 </span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="n">attr_name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">compiled</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個模式&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># 測試</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nd">@auto_compile_patterns</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">class</span> <span class="nc">TestValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">IMPORT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+module\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;import\s+module&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;print\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="n">NOT_A_PATTERN</span> <span class="o">=</span> <span class="s2">&#34;這不是模式列表&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># 驗證轉換結果</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">IMPORT_PATTERNS 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">IMPORT_PATTERNS</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;OUTPUT_PATTERNS 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">OUTPUT_PATTERNS</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;NOT_A_PATTERN 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">NOT_A_PATTERN</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/re.html">Python re 模組文件</a></li>
<li><a href="https://docs.python.org/3/howto/regex.html">Regular Expression HOWTO</a></li>
<li><a href="https://docs.python.org/3/howto/regex.html#common-problems">正則表達式效能最佳實踐</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></em></p>
]]></content:encoded></item><item><title>案例：同步/非同步橋接</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib&lt;/code> 整體架構，展示如何用 &lt;code>run_in_executor&lt;/code> 和 &lt;code>asyncio.run&lt;/code> 在同步與非同步程式碼之間建立橋樑。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib&lt;/code> 是一個同步設計的 Python 工具庫，包含多個模組：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># .claude/lib/__init__.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">Claude Hooks 共用程式庫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">模組結構:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">- config_loader: 配置檔案載入
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">- hook_io: Hook 輸入輸出處理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2">- hook_validator: Hook 合規性驗證
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2">- markdown_link_checker: Markdown 連結檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">get_project_root&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.config_loader&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">load_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&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;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># git_utils.py - 同步的 subprocess 呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">run_git_command&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">args&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&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">cwd&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&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="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Execute git command and return result&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&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="p">[&lt;/span>&lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">args&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">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&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="n">capture_output&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&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="n">text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># config_loader.py - 同步的檔案 I/O&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Load configuration file&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_config_dir&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="c1"># hook_validator.py - 同步的檔案驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate a single hook file&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... validation logic&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：不需要了解 asyncio，任何 Python 開發者都能使用&lt;/li>
&lt;li>&lt;strong>向後相容&lt;/strong>：可以在任何 Python 環境中執行&lt;/li>
&lt;li>&lt;strong>測試容易&lt;/strong>：同步程式碼的測試更直觀&lt;/li>
&lt;li>&lt;strong>依賴少&lt;/strong>：不需要額外的非同步依賴庫&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>在非同步環境（如 FastAPI、aiohttp）中使用時：&lt;/p>
&lt;h4 id="問題-1阻塞事件迴圈">問題 1：阻塞事件迴圈&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lib.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/git/status&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_git_status&lt;/span>&lt;span class="p">():&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"># BAD: These synchronous calls block the event loop!&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">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># Blocks ~50ms&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">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># Blocks ~50ms&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># During these 100ms, NO other requests can be processed!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;worktrees&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2無法並行執行">問題 2：無法並行執行&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&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="s2">&amp;#34;&amp;#34;&amp;#34;Validate all hooks - sequential execution&amp;#34;&amp;#34;&amp;#34;&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">results&lt;/span> &lt;span class="o">=&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="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&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="c1"># Each validation runs one after another&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># ~10ms each&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10 hooks = 100ms total&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># With parallelization, could be ~10ms!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3無法有效利用等待時間">問題 3：無法有效利用等待時間&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_project_health&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;Check multiple aspects of project health&amp;#34;&amp;#34;&amp;#34;&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"># All I/O operations execute sequentially&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">git_status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># Wait...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Wait...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">check_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;docs/README.md&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Wait...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Total time = sum of all operations&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">git_status&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="s2">&amp;#34;config&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config&lt;/span>&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="s2">&amp;#34;links&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">links&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>在非同步程式碼中安全地呼叫同步函式&lt;/strong>（不阻塞事件迴圈）&lt;/li>
&lt;li>&lt;strong>在同步程式碼中使用非同步函式&lt;/strong>（保持向後相容）&lt;/li>
&lt;li>&lt;strong>避免巢狀事件迴圈問題&lt;/strong>&lt;/li>
&lt;li>&lt;strong>保持原有 API 不變&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1run_in_executor---同步到非同步">步驟 1：run_in_executor - 同步到非同步&lt;/h4>
&lt;p>&lt;code>run_in_executor&lt;/code> 將同步函式放到執行緒池中執行，讓事件迴圈可以繼續處理其他任務。&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib</code> 整體架構，展示如何用 <code>run_in_executor</code> 和 <code>asyncio.run</code> 在同步與非同步程式碼之間建立橋樑。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess</a></li>
<li><a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib</code> 是一個同步設計的 Python 工具庫，包含多個模組：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/lib/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Claude Hooks 共用程式庫
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">模組結構:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">- config_loader: 配置檔案載入
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">- hook_io: Hook 輸入輸出處理
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">- hook_validator: Hook 合規性驗證
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">- markdown_link_checker: Markdown 連結檢查
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="kn">from</span> <span class="nn">.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>這些函式都是同步的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># git_utils.py - 同步的 subprocess 呼叫</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Execute git command and return result&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;git&#34;</span><span class="p">]</span> <span class="o">+</span> <span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">capture_output</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># config_loader.py - 同步的檔案 I/O</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load configuration file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">config_dir</span> <span class="o">=</span> <span class="n">get_config_dir</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">yaml_path</span> <span class="o">=</span> <span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># hook_validator.py - 同步的檔案驗證</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">hook_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># ... validation logic</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：不需要了解 asyncio，任何 Python 開發者都能使用</li>
<li><strong>向後相容</strong>：可以在任何 Python 環境中執行</li>
<li><strong>測試容易</strong>：同步程式碼的測試更直觀</li>
<li><strong>依賴少</strong>：不需要額外的非同步依賴庫</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>在非同步環境（如 FastAPI、aiohttp）中使用時：</p>
<h4 id="問題-1阻塞事件迴圈">問題 1：阻塞事件迴圈</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">get_worktree_list</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_git_status</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># BAD: These synchronous calls block the event loop!</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>      <span class="c1"># Blocks ~50ms</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>    <span class="c1"># Blocks ~50ms</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># During these 100ms, NO other requests can be processed!</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span><span class="p">,</span> <span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span><span class="p">}</span></span></span></code></pre></div><h4 id="問題-2無法並行執行">問題 2：無法並行執行</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate all hooks - sequential execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">Path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1"># Each validation runs one after another</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>  <span class="c1"># ~10ms each</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 10 hooks = 100ms total</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># With parallelization, could be ~10ms!</span></span></span></code></pre></div><h4 id="問題-3無法有效利用等待時間">問題 3：無法有效利用等待時間</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_project_health</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check multiple aspects of project health&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># All I/O operations execute sequentially</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">git_status</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">])</span>      <span class="c1"># Wait...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>                       <span class="c1"># Wait...</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="n">check_markdown_links</span><span class="p">(</span><span class="s2">&#34;docs/README.md&#34;</span><span class="p">)</span>       <span class="c1"># Wait...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Total time = sum of all operations</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;git&#34;</span><span class="p">:</span> <span class="n">git_status</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;config&#34;</span><span class="p">:</span> <span class="n">config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;links&#34;</span><span class="p">:</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>在非同步程式碼中安全地呼叫同步函式</strong>（不阻塞事件迴圈）</li>
<li><strong>在同步程式碼中使用非同步函式</strong>（保持向後相容）</li>
<li><strong>避免巢狀事件迴圈問題</strong></li>
<li><strong>保持原有 API 不變</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1run_in_executor---同步到非同步">步驟 1：run_in_executor - 同步到非同步</h4>
<p><code>run_in_executor</code> 將同步函式放到執行緒池中執行，讓事件迴圈可以繼續處理其他任務。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><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"># Create a shared thread pool for I/O operations</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">_io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;async_io&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">run_sync_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Run a synchronous function in a thread pool executor.
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    This prevents blocking the event loop when calling
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    synchronous I/O operations from async code.
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        func: The synchronous function to execute
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        *args: Positional arguments for the function
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        **kwargs: Keyword arguments for the function
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        The result of the function call
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        # Instead of blocking:
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        result = sync_function(arg1, arg2)
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        # Use executor:
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">        result = await run_sync_in_executor(sync_function, arg1, arg2)
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># functools.partial handles kwargs</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">_io_executor</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span></span></span></code></pre></div><h4 id="使用範例包裝現有同步函式">使用範例：包裝現有同步函式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">get_worktree_list</span><span class="p">,</span> <span class="n">run_git_command</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="n">load_config</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_validator</span> <span class="kn">import</span> <span class="n">validate_hook</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Async wrappers for existing sync functions</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for get_current_branch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for get_worktree_list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">get_worktree_list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_load_config</span><span class="p">(</span><span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for load_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_validate_hook</span><span class="p">(</span><span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for validate_hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">validate_hook</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2asynciorun---非同步到同步">步驟 2：asyncio.run - 非同步到同步</h4>
<p>當你有非同步程式碼，但需要從同步環境呼叫時，使用 <code>asyncio.run</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Coroutine</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">run_async</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Run an async function from synchronous code.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Creates a new event loop if none exists.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Safe to call from synchronous entry points.
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        coro: The coroutine to execute
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">        The result of the coroutine
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        # From a synchronous script:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        result = run_async(async_function(arg1, arg2))
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    Warning:
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        Cannot be called when an event loop is already running!
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Use nest_asyncio or redesign your code structure.
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># Synchronous API that uses async implementation internally</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">def</span> <span class="nf">get_all_worktree_branches</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Get branches for all worktrees.
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    Uses async implementation internally for parallelization,
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    but provides a synchronous API for compatibility.
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_async_impl</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">tasks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_get_branch_for_path</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="nb">zip</span><span class="p">([</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">],</span> <span class="n">results</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">return</span> <span class="n">run_async</span><span class="p">(</span><span class="n">_async_impl</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">_get_branch_for_path</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Get current branch for a specific path&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">cwd</span><span class="o">=</span><span class="n">path</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span></span></span></code></pre></div><h4 id="步驟-3處理已存在的事件迴圈">步驟 3：處理已存在的事件迴圈</h4>
<p>當你在已有事件迴圈的環境中（如 Jupyter Notebook、某些 Web 框架），直接呼叫 <code>asyncio.run</code> 會失敗：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># This will raise RuntimeError in Jupyter or when loop is running</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">some_coroutine</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># RuntimeError: asyncio.run() cannot be called from a running event loop</span></span></span></code></pre></div><h5 id="解決方案-a使用-nest_asyncio快速修復">解決方案 A：使用 nest_asyncio（快速修復）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># pip install nest-asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">nest_asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">run_async_safe</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    Run async code safely, even from a running event loop.
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Uses nest_asyncio to allow nested event loops.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    This is a pragmatic solution for environments like Jupyter.
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        nest_asyncio patches the event loop globally.
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        Use with caution in production code.
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># No running loop - safe to use asyncio.run</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># Running loop exists - need to nest</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">nest_asyncio</span><span class="o">.</span><span class="n">apply</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span></span></span></code></pre></div><h5 id="解決方案-b偵測執行環境推薦">解決方案 B：偵測執行環境（推薦）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Coroutine</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">is_event_loop_running</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check if there&#39;s a running event loop&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">def</span> <span class="nf">run_async_adaptive</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Run async code with automatic environment detection.
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    - If no event loop: uses asyncio.run()
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    - If loop running: uses run_in_executor to run in a new thread
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    This is safer than nest_asyncio for production use.
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">if</span> <span class="n">is_event_loop_running</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># We&#39;re in an async context - run in a new thread</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="kn">import</span> <span class="nn">concurrent.futures</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">with</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">future</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">,</span> <span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">return</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="c1"># Safe to run directly</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span></span></span></code></pre></div><h5 id="解決方案-c提供雙重-api最佳實踐">解決方案 C：提供雙重 API（最佳實踐）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">GitUtils</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Git utilities with both sync and async APIs.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Usage:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        # Synchronous (traditional)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        utils = GitUtils()
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        branch = utils.get_current_branch()
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        # Asynchronous
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        branch = await utils.async_get_current_branch()
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cwd</span> <span class="o">=</span> <span class="n">cwd</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># ===== Synchronous API (original) =====</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">get_current_branch</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get current branch (sync)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">cwd</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">get_worktree_list</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get worktree list (sync)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="c1"># ... original implementation</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># ===== Asynchronous API =====</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get current branch (async)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get worktree list (async)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">get_worktree_list</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4建立統一的-api">步驟 4：建立統一的 API</h4>
<p>整合以上模式，建立一個統一的適配器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Sync/Async Bridge Adapter
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">Provides unified access to .claude/lib modules from both
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">synchronous and asynchronous contexts.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Coroutine</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"># Shared executor for I/O operations</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="n">_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;lib_async&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">AsyncAdapter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    Adapter for converting sync functions to async.
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    Provides both decorator and wrapper patterns for flexibility.
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        adapter = AsyncAdapter()
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">        # As decorator
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        @adapter.make_async
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">        def sync_function(x):
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">            return x * 2
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">        result = await sync_function(5)
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        # As wrapper
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        result = await adapter.run(other_sync_function, arg1, arg2)
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">executor</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ThreadPoolExecutor</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">executor</span> <span class="o">=</span> <span class="n">executor</span> <span class="ow">or</span> <span class="n">_executor</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Run a sync function asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">executor</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">def</span> <span class="nf">make_async</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        Decorator to create async version of sync function.
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">            @adapter.make_async
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">            def slow_io_operation(path: str) -&gt; str:
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">                with open(path) as f:
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">                    return f.read()
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">            # Now can be awaited
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">            content = await slow_io_operation(&#34;file.txt&#34;)
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="k">class</span> <span class="nc">SyncAdapter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="s2">    Adapter for calling async functions from sync code.
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">        adapter = SyncAdapter()
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">        # Run async function synchronously
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        result = adapter.run(async_function(arg1, arg2))
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="nd">@staticmethod</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">        Run a coroutine from synchronous code.
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">        Automatically handles the case when an event loop
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">        is already running.
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="c1"># Loop is running - use thread</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="kn">import</span> <span class="nn">concurrent.futures</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="k">with</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="n">ex</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">future</span> <span class="o">=</span> <span class="n">ex</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">,</span> <span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="k">return</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="c1"># No running loop - safe to use asyncio.run</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="nd">@staticmethod</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="k">def</span> <span class="nf">make_sync</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">async_func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="s2">        Decorator to create sync version of async function.
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="s2">            @SyncAdapter.make_sync
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="s2">            async def async_fetch(url: str) -&gt; str:
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="s2">                async with aiohttp.get(url) as resp:
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="s2">                    return await resp.text()
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="s2">            # Now can be called synchronously
</span></span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="s2">            content = async_fetch(&#34;https://example.com&#34;)
</span></span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">async_func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">            <span class="n">coro</span> <span class="o">=</span> <span class="n">async_func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="k">return</span> <span class="n">SyncAdapter</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Sync/Async Bridge for .claude/lib
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">This module provides async wrappers for the synchronous
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">.claude/lib modules, enabling their use in async contexts
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">like FastAPI without blocking the event loop.
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">Usage:
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    # In async code (FastAPI, aiohttp, etc.)
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    from lib_async import (
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">        async_get_current_branch,
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">        async_load_config,
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        async_validate_hooks,
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">    )
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="s2">    branch = await async_get_current_branch()
</span></span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="s2">    # In sync code that needs parallelization
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    from lib_async import parallel_validate_hooks
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    results = parallel_validate_hooks(hooks_dir)
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Coroutine</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"># Import original sync modules</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">load_quality_rules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_validator</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">validate_all_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">ValidationResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.markdown_link_checker</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">check_markdown_links</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">check_directory</span> <span class="k">as</span> <span class="n">check_markdown_directory</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">LinkCheckResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"># ===== Shared Resources =====</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="c1"># Thread pool for I/O-bound sync operations</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="n">_io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;lib_async_io&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="c1"># ===== Core Utilities =====</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">run_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">    Run a synchronous function in the thread pool executor.
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        func: Synchronous function to execute
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">        *args: Positional arguments
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="s2">        **kwargs: Keyword arguments
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">        Function result
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">_io_executor</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="k">def</span> <span class="nf">make_async</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">    Decorator to create async version of a sync function.
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        @make_async
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        def slow_operation(x):
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">            time.sleep(1)
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">            return x * 2
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        result = await slow_operation(5)
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"># ===== Async Git Utils =====</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of run_git_command&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">run_git_command</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_current_branch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_project_root</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_project_root&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_worktree_list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_worktree_list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="c1"># ===== Async Config Loader =====</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">
</span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_load_config</span><span class="p">(</span><span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_agents_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_agents_config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_quality_rules&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_quality_rules</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="c1"># ===== Async Hook Validator =====</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_validate_hook</span><span class="p">(</span><span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of validate_hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">validate_hook</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">    <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="s2">    Validate all hooks in parallel.
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="s2">    Unlike the sync version that validates sequentially,
</span></span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="s2">    this version validates all hooks concurrently.
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="k">await</span> <span class="n">async_get_project_root</span><span class="p">())</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="n">hooks_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="c1"># Collect all hook files</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">        <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">f</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="c1"># Validate all in parallel</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">async_validate_hook</span><span class="p">(</span><span class="n">hook</span><span class="p">)</span> <span class="k">for</span> <span class="n">hook</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="c1"># ===== Async Markdown Link Checker =====</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_check_markdown_links</span><span class="p">(</span><span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of check_markdown_links&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">check_markdown_links</span><span class="p">,</span> <span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_check_markdown_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="s2">    Check all markdown files in directory in parallel.
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">    <span class="c1"># Get file list synchronously (fast operation)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="n">dir_path_obj</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path_obj</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path_obj</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path_obj</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">    <span class="c1"># Check all files in parallel</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">async_check_markdown_links</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">))</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="c1"># ===== Parallel Operations =====</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_check_all_worktrees</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="s2">    Check status of all worktrees in parallel.
</span></span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">        dict: {path: {&#34;branch&#34;: str, &#34;status&#34;: str, &#34;is_clean&#34;: bool}}
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">
</span></span><span class="line"><span class="ln">218</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="c1"># Run git status and branch in parallel for each worktree</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">status_task</span> <span class="o">=</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">        <span class="n">branch_task</span> <span class="o">=</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="p">(</span><span class="n">status_ok</span><span class="p">,</span> <span class="n">status</span><span class="p">),</span> <span class="p">(</span><span class="n">branch_ok</span><span class="p">,</span> <span class="n">branch</span><span class="p">)</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">status_task</span><span class="p">,</span> <span class="n">branch_task</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">
</span></span><span class="line"><span class="ln">229</span><span class="cl">        <span class="k">return</span> <span class="n">path</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span> <span class="k">if</span> <span class="n">branch_ok</span> <span class="k">else</span> <span class="n">wt</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status_ok</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="s2">&#34;is_clean&#34;</span><span class="p">:</span> <span class="n">status_ok</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">status</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">
</span></span><span class="line"><span class="ln">235</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_project_health_check</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">242</span><span class="cl"><span class="s2">    Comprehensive project health check with parallel execution.
</span></span></span><span class="line"><span class="ln">243</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="s2">    Checks:
</span></span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="s2">    - Git status
</span></span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="s2">    - Configuration validity
</span></span></span><span class="line"><span class="ln">247</span><span class="cl"><span class="s2">    - Hook compliance
</span></span></span><span class="line"><span class="ln">248</span><span class="cl"><span class="s2">    - Documentation links
</span></span></span><span class="line"><span class="ln">249</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">250</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">251</span><span class="cl"><span class="s2">        dict: Health check results
</span></span></span><span class="line"><span class="ln">252</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">    <span class="c1"># Run all checks in parallel</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="n">git_task</span> <span class="o">=</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="n">worktrees_task</span> <span class="o">=</span> <span class="n">async_check_all_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="n">config_task</span> <span class="o">=</span> <span class="n">async_load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">hooks_task</span> <span class="o">=</span> <span class="n">async_validate_all_hooks</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="n">branch</span><span class="p">,</span> <span class="n">worktrees</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">hook_results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="n">git_task</span><span class="p">,</span> <span class="n">worktrees_task</span><span class="p">,</span> <span class="n">config_task</span><span class="p">,</span> <span class="n">hooks_task</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">        <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span>  <span class="c1"># Don&#39;t fail if one check fails</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="s2">&#34;git&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="nb">str</span><span class="p">(</span><span class="n">branch</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="s2">&#34;config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">            <span class="s2">&#34;loaded&#34;</span><span class="p">:</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="s2">&#34;agents_count&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;known_agents&#34;</span><span class="p">,</span> <span class="p">[]))</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="s2">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">            <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_results</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hook_results</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">            <span class="s2">&#34;compliant&#34;</span><span class="p">:</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">hook_results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hook_results</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">
</span></span><span class="line"><span class="ln">279</span><span class="cl"><span class="c1"># ===== Sync Wrappers for Async Functions =====</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl"><span class="k">def</span> <span class="nf">parallel_validate_hooks</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">283</span><span class="cl"><span class="s2">    Synchronous API that uses async parallelization internally.
</span></span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="s2">    Use this when you want parallelization but are in sync context.
</span></span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_validate_all_hooks</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">
</span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="k">def</span> <span class="nf">parallel_check_worktrees</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">291</span><span class="cl"><span class="s2">    Synchronous API for parallel worktree checking.
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_check_all_worktrees</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">
</span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="k">def</span> <span class="nf">project_health_check</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="s2">    Synchronous API for comprehensive health check.
</span></span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_project_health_check</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">
</span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Demonstrate the sync/async bridge capabilities&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">
</span></span><span class="line"><span class="ln">307</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Sync/Async Bridge Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">310</span><span class="cl">
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="c1"># 1. Basic async wrapper usage</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">1. Basic async wrapper:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Current branch: </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="c1"># 2. Parallel execution comparison</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Parallel vs Sequential comparison:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="c1"># Sequential (simulated)</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">_</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">        <span class="n">seq_time</span> <span class="o">+=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Sequential check: </span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="c1"># Parallel</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_check_all_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="n">par_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Parallel check: </span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Speedup: </span><span class="si">{</span><span class="n">seq_time</span><span class="o">/</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="c1"># 3. Project health check</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. Project health check (parallel):&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="n">health</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_project_health_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Branch: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Worktrees: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;worktrees&#39;</span><span class="p">])</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Hooks compliant: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;compliant&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;total&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Time: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">
</span></span><span class="line"><span class="ln">349</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="在-fastapi-中使用同步函式">在 FastAPI 中使用同步函式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">HTTPException</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib_async</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">async_get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">async_get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">async_check_all_worktrees</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">async_validate_all_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">async_project_health_check</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    Get current git branch.
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Uses async wrapper to prevent blocking the event loop.
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">if</span> <span class="n">branch</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">&#34;Not a git repository&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/worktrees&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_worktrees</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    Get all worktrees with their status.
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Checks all worktrees in parallel for fast response.
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_check_all_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">health_check</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">    Comprehensive project health check.
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    Runs multiple checks in parallel:
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    - Git status
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    - Configuration
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    - Hook compliance
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">health</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_project_health_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="n">health</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/hooks/validate&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">validate_hooks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">    Validate all hooks in parallel.
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">    Much faster than sequential validation for many hooks.
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_validate_all_hooks</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="s2">&#34;compliant&#34;</span><span class="p">:</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="s2">&#34;issues&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">                <span class="s2">&#34;hook&#34;</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">hook_path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="s2">&#34;issues&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                    <span class="p">{</span><span class="s2">&#34;level&#34;</span><span class="p">:</span> <span class="n">i</span><span class="o">.</span><span class="n">level</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">i</span><span class="o">.</span><span class="n">message</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><h4 id="在同步腳本中使用非同步函式">在同步腳本中使用非同步函式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Synchronous script that leverages async parallelization internally.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">from</span> <span class="nn">lib_async</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">parallel_validate_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">parallel_check_worktrees</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">project_health_check</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Project Health Report&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># These functions use asyncio internally for parallelization</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># but provide a synchronous API</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 1. Check all worktrees in parallel</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">1. Worktree Status:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">parallel_check_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;clean&#34;</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&#34;is_clean&#34;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;dirty&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   [</span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 2. Validate all hooks in parallel</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Hook Validation:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">parallel_validate_hooks</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Compliant: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;     [</span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># 3. Comprehensive health check</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. Overall Health:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">health</span> <span class="o">=</span> <span class="n">project_health_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Git branch: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Config loaded: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;config&#39;</span><span class="p">][</span><span class="s1">&#39;loaded&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Hooks OK: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;compliant&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;total&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h4 id="python-39-使用-asyncioto_thread">Python 3.9+ 使用 asyncio.to_thread</h4>
<p>Python 3.9 引入了 <code>asyncio.to_thread</code>，提供更簡潔的語法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">run_git_command</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="n">load_config</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Python 3.9+ simplified syntax</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch_39</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread (Python 3.9+)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_load_config_39</span><span class="p">(</span><span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread (Python 3.9+)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_run_git_command_39</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">args</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">cwd</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread with keyword arguments&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># to_thread supports kwargs directly in Python 3.9+</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">run_git_command</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># Comparison: run_in_executor vs to_thread</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">comparison_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    asyncio.to_thread vs run_in_executor
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    to_thread advantages:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    - Simpler syntax
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    - Better default executor management
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">    - Direct kwargs support
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    run_in_executor advantages:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">    - Works on Python 3.7+
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    - Can use custom executors
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    - More control over thread pool
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="c1"># run_in_executor (Python 3.7+)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">result1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1"># to_thread (Python 3.9+)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">result2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">assert</span> <span class="n">result1</span> <span class="o">==</span> <span class="n">result2</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>run_in_executor</th>
          <th>asyncio.run</th>
          <th>asyncio.to_thread</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>方向</strong></td>
          <td>同步 -&gt; 非同步</td>
          <td>非同步 -&gt; 同步</td>
          <td>同步 -&gt; 非同步</td>
      </tr>
      <tr>
          <td><strong>執行緒</strong></td>
          <td>使用執行緒池</td>
          <td>建立新事件迴圈</td>
          <td>使用預設執行緒池</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>非同步環境呼叫同步 I/O</td>
          <td>同步入口點執行非同步</td>
          <td>非同步環境呼叫同步 I/O</td>
      </tr>
      <tr>
          <td><strong>限制</strong></td>
          <td>需要事件迴圈存在</td>
          <td>不能巢狀呼叫</td>
          <td>需要 Python 3.9+</td>
      </tr>
      <tr>
          <td><strong>效能</strong></td>
          <td>高（可重用執行緒）</td>
          <td>中（建立新迴圈開銷）</td>
          <td>高（優化的執行緒池）</td>
      </tr>
      <tr>
          <td><strong>複雜度</strong></td>
          <td>中</td>
          <td>低</td>
          <td>低</td>
      </tr>
  </tbody>
</table>
<h3 id="threadpoolexecutor-vs-processpoolexecutor">ThreadPoolExecutor vs ProcessPoolExecutor</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>ThreadPoolExecutor</th>
          <th>ProcessPoolExecutor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>I/O 密集操作</td>
          <td>CPU 密集操作</td>
      </tr>
      <tr>
          <td><strong>記憶體</strong></td>
          <td>共享記憶體</td>
          <td>獨立記憶體空間</td>
      </tr>
      <tr>
          <td><strong>GIL</strong></td>
          <td>受 GIL 限制</td>
          <td>繞過 GIL</td>
      </tr>
      <tr>
          <td><strong>啟動成本</strong></td>
          <td>低</td>
          <td>高（進程建立）</td>
      </tr>
      <tr>
          <td><strong>資料傳遞</strong></td>
          <td>直接傳遞</td>
          <td>需要序列化</td>
      </tr>
  </tbody>
</table>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># I/O-bound: use ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># CPU-bound: use ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">cpu_executor</span> <span class="o">=</span> <span class="n">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">io_bound_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;File I/O, network calls, subprocess&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">io_executor</span><span class="p">,</span> <span class="n">sync_io_function</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">cpu_bound_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Heavy computation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">cpu_executor</span><span class="p">,</span> <span class="n">sync_cpu_function</span><span class="p">)</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>漸進式遷移到 asyncio</strong>：有大量同步程式碼，需要逐步遷移</li>
<li><strong>在 FastAPI 中使用同步第三方庫</strong>：如 <code>requests</code>、<code>boto3</code>、資料庫驅動</li>
<li><strong>提供同步/非同步雙 API</strong>：讓使用者選擇適合的模式</li>
<li><strong>並行化現有同步操作</strong>：如批次檔案處理、多 API 呼叫</li>
<li><strong>整合傳統程式碼</strong>：舊系統的同步函式需要在新非同步系統中使用</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>全新專案</strong>：直接用原生 asyncio 設計</li>
<li><strong>CPU 密集操作</strong>：應用 <code>multiprocessing</code> 或 <code>ProcessPoolExecutor</code></li>
<li><strong>簡單的單一操作</strong>：不需要並行的情況</li>
<li><strong>效能極度敏感</strong>：執行緒池有微小開銷</li>
<li><strong>已有原生非同步替代方案</strong>：如用 <code>aiohttp</code> 替代 <code>requests</code></li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>用 run_in_executor 包裝 requests.get</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># TODO: Implement this function</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Fetch URL content asynchronously using requests library.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Hint: Use run_in_executor to wrap requests.get
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># Test your implementation</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://httpbin.org/get&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">content</span><span class="p">[:</span><span class="mi">200</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">test</span><span class="p">())</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fetch URL content asynchronously using requests&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">fetch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Or using to_thread (Python 3.9+)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch_39</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">fetch</span><span class="p">)</span></span></span></code></pre></div></details>
<h3 id="進階練習">進階練習</h3>
<ol start="2">
<li><strong>建立支援同步/非同步雙模式的 API 客戶端</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># TODO: Implement a dual-mode API client</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">WeatherClient</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    Weather API client supporting both sync and async modes.
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    Usage:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        client = WeatherClient(api_key=&#34;xxx&#34;)
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        # Sync mode
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        weather = client.get_weather(&#34;Tokyo&#34;)
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        # Async mode
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        weather = await client.async_get_weather(&#34;Tokyo&#34;)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">api_key</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">api_key</span> <span class="o">=</span> <span class="n">api_key</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">base_url</span> <span class="o">=</span> <span class="s2">&#34;https://api.weatherapi.com/v1&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Synchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Asynchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_multiple</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cities</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Fetch weather for multiple cities in parallel&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">WeatherClient</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">api_key</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">api_key</span> <span class="o">=</span> <span class="n">api_key</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">base_url</span> <span class="o">=</span> <span class="s2">&#34;https://api.weatherapi.com/v1&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Synchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">base_url</span><span class="si">}</span><span class="s2">/current.json&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">url</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">params</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;key&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">api_key</span><span class="p">,</span> <span class="s2">&#34;q&#34;</span><span class="p">:</span> <span class="n">city</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Asynchronous weather fetch using run_in_executor&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_weather</span><span class="p">,</span> <span class="n">city</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_multiple</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cities</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Fetch weather for multiple cities in parallel&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">async_get_weather</span><span class="p">(</span><span class="n">city</span><span class="p">)</span> <span class="k">for</span> <span class="n">city</span> <span class="ow">in</span> <span class="n">cities</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">city</span><span class="p">:</span> <span class="n">result</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">result</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">for</span> <span class="n">city</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">cities</span><span class="p">,</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="p">}</span></span></span></code></pre></div></details>
<h3 id="挑戰題">挑戰題</h3>
<ol start="3">
<li><strong>實作自動偵測執行環境的適配器</strong></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># TODO: Implement an adaptive function caller</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveCaller</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    Automatically detects the execution context and calls
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    functions appropriately.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    - In async context: awaits coroutines, wraps sync functions
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - In sync context: runs async functions, calls sync directly
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Usage:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        caller = AdaptiveCaller()
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        # Works in both sync and async contexts!
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        result = caller.call(some_function, arg1, arg2)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        Call a function adaptively based on context.
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        - If func is async and we&#39;re in sync: run with asyncio.run
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        - If func is sync and we&#39;re in async: use run_in_executor
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        - Otherwise: call directly
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">inspect</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveCaller</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">_is_async_context</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if we&#39;re in an async context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Call function adaptively based on context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">is_async_func</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">in_async_context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_is_async_context</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="n">is_async_func</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">if</span> <span class="n">in_async_context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                <span class="c1"># Return coroutine to be awaited</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="c1"># Run async function in new event loop</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">if</span> <span class="n">in_async_context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="c1"># Wrap sync function for async context</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wrap_sync</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="c1"># Call sync function directly</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_wrap_sync</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Wrap sync function for async execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_executor</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_executor</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">call_async</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Explicitly async version of call&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">is_async_func</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="k">if</span> <span class="n">is_async_func</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wrap_sync</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="c1"># Usage example</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="n">caller</span> <span class="o">=</span> <span class="n">AdaptiveCaller</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="k">def</span> <span class="nf">sync_func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"># In sync context</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="n">result1</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">sync_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>      <span class="c1"># Direct call</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="n">result2</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">async_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>     <span class="c1"># Uses asyncio.run</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># In async context</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="n">result3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">caller</span><span class="o">.</span><span class="n">call_async</span><span class="p">(</span><span class="n">sync_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>   <span class="c1"># Uses executor</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="n">result4</span> <span class="o">=</span> <span class="k">await</span> <span class="n">caller</span><span class="o">.</span><span class="n">call_async</span><span class="p">(</span><span class="n">async_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>  <span class="c1"># Direct await</span></span></span></code></pre></div></details>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor">run_in_executor 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-runner.html#asyncio.run">asyncio.run 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread">asyncio.to_thread 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/case-studies/parallel-io/" data-link-title="案例：並行 I/O 操作" data-link-desc="用 asyncio.gather 和 TaskGroup 實現高效的並行 I/O 操作">並行 I/O 操作</a></em>
<em>返回：<a href="/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計</a></em></p>
]]></content:encoded></item><item><title>案例：使用 Hatch 完整工作流</title><link>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/hatch-workflow/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/hatch-workflow/</guid><description>&lt;p>本案例展示如何使用 Hatch 這個 PyPA 推薦的現代 Python 專案管理工具，完成從專案建立到發布的完整流程。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/build-systems/" data-link-title="6.2 建構系統比較" data-link-desc="比較 setuptools、Poetry、Hatch 等建構系統">6.2 建構系統比較&lt;/a>&lt;/li>
&lt;li>Python 虛擬環境基礎&lt;/li>
&lt;li>基本的命令列操作&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="hatch-是什麼">Hatch 是什麼？&lt;/h3>
&lt;p>Hatch 是由 PyPA（Python Packaging Authority）成員開發維護的現代 Python 專案管理工具，整合了：&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">Hatch 功能整合：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── 專案腳手架（hatch new）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── 環境管理（類似 tox + virtualenv）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── 版本管理（自動更新版本號）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── 建構系統（hatchling）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">└── 發布工具（hatch publish）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼選擇-hatch">為什麼選擇 Hatch？&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;strong>標準優先&lt;/strong>&lt;/td>
 &lt;td>完全遵循 PEP 517/518/621 標準&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>一站式工具&lt;/strong>&lt;/td>
 &lt;td>不需要額外安裝 tox、virtualenv、bump2version&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>快速建構&lt;/strong>&lt;/td>
 &lt;td>hatchling 建構速度優於 setuptools&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>環境矩陣&lt;/strong>&lt;/td>
 &lt;td>內建多 Python 版本測試支援&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>腳本系統&lt;/strong>&lt;/td>
 &lt;td>定義可重用的專案腳本&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="完整工作流">完整工作流&lt;/h2>
&lt;h3 id="第一步安裝-hatch">第一步：安裝 Hatch&lt;/h3>





&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">&lt;span class="c1"># 使用 pip 安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">pip install hatch
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或使用 pipx（推薦，隔離安裝）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">pipx install hatch
&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"># 驗證安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">hatch --version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="第二步建立新專案">第二步：建立新專案&lt;/h3>





&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">&lt;span class="c1"># 建立新專案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hatch new my-awesome-lib
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 互動式建立（可自訂選項）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">hatch new my-awesome-lib --init
&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"># 建立應用程式專案（非函式庫）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">hatch new --cli my-cli-app&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="預設專案結構">預設專案結構&lt;/h4>





&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">my-awesome-lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ └── my_awesome_lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ └── __about__.py # 版本資訊
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ └── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── pyproject.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── LICENSE.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="生成的-pyprojecttoml">生成的 pyproject.toml&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hatchling&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatchling.build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&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 class="nx">project&lt;/span>&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my-awesome-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">dynamic&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">description&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">readme&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">requires-python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;=3.8&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">license&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;MIT&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">keywords&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">13&lt;/span>&lt;span class="cl">&lt;span class="nx">authors&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">14&lt;/span>&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Your Name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">email&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;you@example.com&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">classifiers&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">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Development Status :: 4 - Beta&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.8&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.9&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.10&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.11&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Programming Language :: Python :: 3.12&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">urls&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="nx">Documentation&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib#readme&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="nx">Issues&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib/issues&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="nx">Source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://github.com/yourname/my-awesome-lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_awesome_lib/__about__.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="第三步環境管理hatch-env">第三步：環境管理（hatch env）&lt;/h3>
&lt;h4 id="定義環境">定義環境&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># pyproject.toml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c"># 預設環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&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="nx">dependencies&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest-cov&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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="nx">test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">test-cov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest --cov=my_awesome_lib --cov-report=term-missing {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c"># Lint 環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff&amp;gt;=0.4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;mypy&amp;gt;=1.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nx">check&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">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff check src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff format --check src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="nx">fix&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">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff check --fix src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff format src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="nx">typing&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;mypy src&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;check&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;typing&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="c"># 文件環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">36&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;mkdocs&amp;gt;=1.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;mkdocs-material&amp;gt;=9.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">docs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="nx">build&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;mkdocs build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="nx">serve&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;mkdocs serve&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="使用環境">使用環境&lt;/h4>





&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">&lt;span class="c1"># 顯示所有環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">hatch env show
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行預設環境的腳本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">hatch run &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">hatch run test-cov
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">hatch run lint:check
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">hatch run lint:fix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">hatch run lint:typing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 進入環境 shell&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">hatch shell
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="c1"># 進入特定環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">hatch shell lint
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># 移除環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">hatch env remove
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">hatch env remove lint
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="c1"># 清除所有環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">hatch env prune&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="第四步版本管理hatch-version">第四步：版本管理（hatch version）&lt;/h3>
&lt;h4 id="設定版本來源">設定版本來源&lt;/h4>
&lt;h5 id="方法-a從檔案讀取版本">方法 A：從檔案讀取版本&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&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="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_awesome_lib/__about__.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># src/my_awesome_lib/__about__.py&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">__version__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.1.0&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="方法-b從-git-標籤讀取版本推薦用於開源專案">方法 B：從 Git 標籤讀取版本（推薦用於開源專案）&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hatchling&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;hatch-vcs&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatchling.build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&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 class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&lt;/span>&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 class="nx">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;vcs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hooks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vcs&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="nx">version-file&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_awesome_lib/_version.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="版本操作">版本操作&lt;/h4>





&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">&lt;span class="c1"># 顯示當前版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">hatch version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 設定特定版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">hatch version 1.0.0
&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"># 語意化版本升級&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">hatch version patch &lt;span class="c1"># 0.1.0 → 0.1.1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">hatch version minor &lt;span class="c1"># 0.1.1 → 0.2.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">hatch version major &lt;span class="c1"># 0.2.0 → 1.0.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 預發布版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">hatch version alpha &lt;span class="c1"># 1.0.0 → 1.0.1a0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">hatch version beta &lt;span class="c1"># 1.0.1a0 → 1.0.1b0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">hatch version rc &lt;span class="c1"># 1.0.1b0 → 1.0.1rc0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">hatch version release &lt;span class="c1"># 1.0.1rc0 → 1.0.1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># 開發版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">hatch version dev &lt;span class="c1"># 1.0.0 → 1.0.1.dev0&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="第五步建構與發布">第五步：建構與發布&lt;/h3>
&lt;h4 id="建構套件">建構套件&lt;/h4>





&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">&lt;span class="c1"># 建構 wheel 和 sdist&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">hatch build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只建構 wheel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">hatch build --target wheel
&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"># 只建構 sdist&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">hatch build --target sdist
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 清除建構產物後重建&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">hatch build --clean&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="建構產物">建構產物&lt;/h5>





&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">dist/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── my_awesome_lib-0.1.0-py3-none-any.whl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">└── my_awesome_lib-0.1.0.tar.gz&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="發布套件">發布套件&lt;/h4>





&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">&lt;span class="c1"># 發布到 PyPI（需要設定認證）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hatch publish
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 發布到 TestPyPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">hatch publish --repo &lt;span class="nb">test&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"># 指定發布的檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">hatch publish dist/my_awesome_lib-0.1.0-py3-none-any.whl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="設定-pypi-認證">設定 PyPI 認證&lt;/h5>





&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">&lt;span class="c1"># 設定 PyPI token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hatch config &lt;span class="nb">set&lt;/span> pypi.auth.username __token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">hatch config &lt;span class="nb">set&lt;/span> pypi.auth.password pypi-xxxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或使用環境變數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">HATCH_INDEX_USER&lt;/span>&lt;span class="o">=&lt;/span>__token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">HATCH_INDEX_AUTH&lt;/span>&lt;span class="o">=&lt;/span>pypi-xxxxx&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="pyprojecttoml-的-hatch-特定設定">pyproject.toml 的 Hatch 特定設定&lt;/h2>
&lt;h3 id="toolhatchbuild-建構設定">[tool.hatch.build] 建構設定&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&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="c"># 包含的檔案（支援 glob）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">include&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;src/my_awesome_lib&amp;#34;&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="s2">&amp;#34;README.md&amp;#34;&lt;/span>&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 class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c"># 排除的檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">exclude&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="s2">&amp;#34;*.pyc&amp;#34;&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="s2">&amp;#34;__pycache__&amp;#34;&lt;/span>&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="s2">&amp;#34;.git&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c"># 是否可重現建構&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">reproducible&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c"># 開發模式設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="nx">dev-mode-dirs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">targets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">sdist&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="c"># 原始碼發布設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="nx">include&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">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/src&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/README.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;/LICENSE.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">targets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">wheel&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="c"># Wheel 發布設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="nx">packages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src/my_awesome_lib&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="c"># 只包含特定平台&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="c"># only-include = [&amp;#34;my_awesome_lib&amp;#34;]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="toolhatchenvs-環境設定">[tool.hatch.envs] 環境設定&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&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="c"># 相依性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c"># 額外安裝的 features&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nx">features&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;yaml&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c"># 環境變數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env-vars&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="nx">PYTHONPATH&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">LOG_LEVEL&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;DEBUG&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c"># 腳本定義&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nx">test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest {args}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c"># 平台特定設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">overrides&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="nx">platform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">windows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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">20&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;test = &amp;#34;pytest --no-header {args}&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="toolhatchversion-版本設定">[tool.hatch.version] 版本設定&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 從檔案讀取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&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="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_awesome_lib/__about__.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nx">pattern&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^__version__ = [&amp;#39;\&amp;#34;](/python-advanced/07-packaging/case-studies/hatch-workflow/?P&amp;lt;version&amp;gt;[^&amp;#39;\&amp;#34;]+)[&amp;#39;\&amp;#34;]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c"># 從 VCS 讀取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;vcs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="nx">raw-options&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">local_scheme&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;no-local-version&amp;#34;&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>&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 class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hooks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">vcs&lt;/span>&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="nx">version-file&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_awesome_lib/_version.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="toolhatchmetadata-元資料設定">[tool.hatch.metadata] 元資料設定&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&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="c"># 允許直接依賴（通常應該避免）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">allow-direct-references&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c"># 動態讀取 README&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hooks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fancy-pypi-readme&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">content-type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;text/markdown&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">metadata&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hooks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fancy-pypi-readme&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fragments&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="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="與-poetry-的比較">與 Poetry 的比較&lt;/h2>
&lt;h3 id="設計理念差異">設計理念差異&lt;/h3>





&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">Hatch：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── 遵循 PEP 標準優先
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── 環境管理內建
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── 不提供依賴鎖定
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── 建構系統（hatchling）可獨立使用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">└── 設定完全在 [tool.hatch]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">Poetry：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── 自有生態系統
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">├── 強調依賴鎖定（poetry.lock）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">├── 虛擬環境管理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">├── poetry.core 可獨立使用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">└── 混合 [project] 和 [tool.poetry]&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="功能對照">功能對照&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>Hatch&lt;/th>
 &lt;th>Poetry&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>依賴鎖定&lt;/td>
 &lt;td>不支援&lt;/td>
 &lt;td>poetry.lock&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>環境管理&lt;/td>
 &lt;td>內建矩陣支援&lt;/td>
 &lt;td>單一環境&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PEP 621&lt;/td>
 &lt;td>完全支援&lt;/td>
 &lt;td>Poetry 2.0 支援&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>內建 bump&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;h3 id="pyprojecttoml-比較">pyproject.toml 比較&lt;/h3>
&lt;h4 id="hatch-風格">Hatch 風格&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hatchling&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatchling.build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&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 class="nx">project&lt;/span>&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my-package&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;requests&amp;gt;=2.28&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">optional-dependencies&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="nx">dev&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest&amp;gt;=8.0&amp;#34;&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="nx">features&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="poetry-風格20">Poetry 風格（2.0）&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;poetry-core&amp;gt;=2.0&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;poetry.core.masonry.api&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&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 class="nx">project&lt;/span>&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 class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my-package&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;1.0.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;requests&amp;gt;=2.28&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">poetry&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dev&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&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="nx">pytest&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;^8.0&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="選擇建議">選擇建議&lt;/h3>





&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">選擇 Hatch：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── 開發 Python 函式庫
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── 需要多環境測試
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── 偏好標準優先
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">└── 不需要依賴鎖定
&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">選擇 Poetry：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── 開發應用程式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── 需要嚴格的依賴鎖定
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">├── 團隊習慣 Poetry 工作流
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">└── 需要與現有 Poetry 專案整合&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="實用技巧">實用技巧&lt;/h2>
&lt;h3 id="多環境測試矩陣">多環境測試矩陣&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 定義多 Python 版本測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&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="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;pytest-cov&amp;#34;&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>&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 class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">matrix&lt;/span>&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 class="nx">python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;3.9&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.10&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.11&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.12&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.13&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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="nx">run&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">cov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest --cov=my_awesome_lib {args:tests}&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">&lt;span class="c1"># 在所有矩陣環境執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">hatch run test:run
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在特定版本執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">hatch run +py&lt;span class="o">=&lt;/span>3.12 test:run
&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"># 顯示矩陣環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">hatch env show --ascii&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="複合腳本">複合腳本&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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="c"># 單一命令&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="nx">test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c"># 多命令（依序執行）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nx">ci&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff check src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest --cov&amp;#34;&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="s2">&amp;#34;mypy src&amp;#34;&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="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c"># 呼叫其他腳本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;lint:check&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;test:cov&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c"># 帶預設參數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nx">lint&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;ruff check {args:.}&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="環境繼承">環境繼承&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># 基礎環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">base&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="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c"># 繼承基礎環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">coverage&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">template&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;base&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest-cov&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">coverage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&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="nx">run&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest --cov {args:tests}&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="條件依賴">條件依賴&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&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="nx">dependencies&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&amp;#34;&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="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c"># 根據平台新增依賴&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">overrides&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">platform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">linux&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest-xdist&amp;#34;&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="nx">platform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">darwin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;pytest-xdist&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c"># 根據 Python 版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">python&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="mf">3.8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">dependencies&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;typing-extensions&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="自訂建構-hook">自訂建構 Hook&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hooks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">custom&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="c"># 建構前執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatch_build.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># hatch_build.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">hatchling.builders.hooks.plugin.interface&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BuildHookInterface&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CustomBuildHook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BuildHookInterface&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="k">def&lt;/span> &lt;span class="nf">initialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">build_data&lt;/span>&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 class="c1"># 建構前的自訂邏輯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Building version &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">version&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="完整範例cli-應用程式">完整範例：CLI 應用程式&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c"># pyproject.toml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">build-system&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="nx">requires&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hatchling&amp;#34;&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="nx">build-backend&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;hatchling.build&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my-cli&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="nx">dynamic&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;version&amp;#34;&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="nx">description&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;A useful CLI tool&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">readme&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nx">requires-python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt;=3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="nx">license&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;MIT&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;click&amp;gt;=8.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;rich&amp;gt;=13.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="nx">my-cli&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;my_cli.main:app&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">project&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">optional-dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nx">yaml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;PyYAML&amp;gt;=6.0&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">version&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="nx">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;src/my_cli/__about__.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">targets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">wheel&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="nx">packages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src/my_cli&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="c"># ===== 環境設定 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">34&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&amp;gt;=8.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest-cov&amp;gt;=4.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">default&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="nx">test&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="nx">cov&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;pytest --cov=my_cli --cov-report=term-missing {args:tests}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="nx">dependencies&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">44&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;ruff&amp;gt;=0.4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;mypy&amp;gt;=1.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">scripts&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="nx">check&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ruff check src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;ruff format --check src tests&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">&lt;span class="nx">fix&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ruff check --fix src tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;ruff format src tests&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">&lt;span class="nx">typing&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;mypy src&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="nx">all&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;check&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;typing&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">hatch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">envs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">matrix&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl">&lt;span class="nx">python&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;3.10&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.11&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.12&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;3.13&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="c"># ===== 工具設定 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ruff&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="nx">src&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl">&lt;span class="nx">line-length&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">88&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ruff&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">lint&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl">&lt;span class="nx">select&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;E&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;W&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;F&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;I&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;B&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;UP&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mypy&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">&lt;span class="nx">python_version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;3.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl">&lt;span class="nx">strict&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">tool&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">pytest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ini_options&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl">&lt;span class="nx">testpaths&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl">&lt;span class="nx">addopts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;-v&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="發布檢查清單">發布檢查清單&lt;/h2>





&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">發布前檢查：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">├── [ ] hatch run lint:all 通過
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">├── [ ] hatch run test:run 在所有 Python 版本通過
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">├── [ ] hatch version 更新版本號
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">├── [ ] 更新 CHANGELOG.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">├── [ ] hatch build 建構成功
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">├── [ ] 在虛擬環境測試安裝：pip install dist/*.whl
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">├── [ ] hatch publish --repo test 發布到 TestPyPI
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">├── [ ] 從 TestPyPI 測試安裝
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">└── [ ] hatch publish 發布到 PyPI&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="延伸閱讀">延伸閱讀&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://hatch.pypa.io/">Hatch 官方文件&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://hatch.pypa.io/latest/plugins/build-hook/reference/">Hatchling 建構後端&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://peps.python.org/pep-0621/">PEP 621 - pyproject.toml 元資料&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://packaging.python.org/">Python 打包使用者指南&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的打包發布案例">案例研究&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本案例展示如何使用 Hatch 這個 PyPA 推薦的現代 Python 專案管理工具，完成從專案建立到發布的完整流程。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/07-packaging/build-systems/" data-link-title="6.2 建構系統比較" data-link-desc="比較 setuptools、Poetry、Hatch 等建構系統">6.2 建構系統比較</a></li>
<li>Python 虛擬環境基礎</li>
<li>基本的命令列操作</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="hatch-是什麼">Hatch 是什麼？</h3>
<p>Hatch 是由 PyPA（Python Packaging Authority）成員開發維護的現代 Python 專案管理工具，整合了：</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">Hatch 功能整合：
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 專案腳手架（hatch new）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── 環境管理（類似 tox + virtualenv）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── 版本管理（自動更新版本號）
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── 建構系統（hatchling）
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── 發布工具（hatch publish）</span></span></code></pre></div><h3 id="為什麼選擇-hatch">為什麼選擇 Hatch？</h3>
<table>
  <thead>
      <tr>
          <th>優勢</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>標準優先</strong></td>
          <td>完全遵循 PEP 517/518/621 標準</td>
      </tr>
      <tr>
          <td><strong>一站式工具</strong></td>
          <td>不需要額外安裝 tox、virtualenv、bump2version</td>
      </tr>
      <tr>
          <td><strong>快速建構</strong></td>
          <td>hatchling 建構速度優於 setuptools</td>
      </tr>
      <tr>
          <td><strong>環境矩陣</strong></td>
          <td>內建多 Python 版本測試支援</td>
      </tr>
      <tr>
          <td><strong>腳本系統</strong></td>
          <td>定義可重用的專案腳本</td>
      </tr>
  </tbody>
</table>
<h2 id="完整工作流">完整工作流</h2>
<h3 id="第一步安裝-hatch">第一步：安裝 Hatch</h3>





<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"><span class="c1"># 使用 pip 安裝</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">pip install hatch
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 或使用 pipx（推薦，隔離安裝）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pipx install hatch
</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"># 驗證安裝</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">hatch --version</span></span></code></pre></div><h3 id="第二步建立新專案">第二步：建立新專案</h3>





<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"><span class="c1"># 建立新專案</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hatch new my-awesome-lib
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 互動式建立（可自訂選項）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">hatch new my-awesome-lib --init
</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"># 建立應用程式專案（非函式庫）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">hatch new --cli my-cli-app</span></span></code></pre></div><h4 id="預設專案結構">預設專案結構</h4>





<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">my-awesome-lib/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│   └── my_awesome_lib/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│       ├── __init__.py
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│       └── __about__.py      # 版本資訊
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── tests/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   └── __init__.py
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── README.md
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── LICENSE.txt</span></span></code></pre></div><h4 id="生成的-pyprojecttoml">生成的 pyproject.toml</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-awesome-lib&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">dynamic</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;version&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">readme</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.8&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">license</span> <span class="p">=</span> <span class="s2">&#34;MIT&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">keywords</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">authors</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">{</span> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Your Name&#34;</span><span class="p">,</span> <span class="nx">email</span> <span class="p">=</span> <span class="s2">&#34;you@example.com&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">classifiers</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="s2">&#34;Development Status :: 4 - Beta&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python :: 3.8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python :: 3.9&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python :: 3.10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python :: 3.11&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="s2">&#34;Programming Language :: Python :: 3.12&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="nx">Documentation</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib#readme&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="nx">Issues</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib/issues&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">Source</span> <span class="p">=</span> <span class="s2">&#34;https://github.com/yourname/my-awesome-lib&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/my_awesome_lib/__about__.py&#34;</span></span></span></code></pre></div><h3 id="第三步環境管理hatch-env">第三步：環境管理（hatch env）</h3>
<h4 id="定義環境">定義環境</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># pyproject.toml</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c"># 預設環境</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="s2">&#34;pytest&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="s2">&#34;pytest-cov&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">test</span> <span class="p">=</span> <span class="s2">&#34;pytest {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">test-cov</span> <span class="p">=</span> <span class="s2">&#34;pytest --cov=my_awesome_lib --cov-report=term-missing {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c"># Lint 環境</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">lint</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="s2">&#34;mypy&gt;=1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">lint</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nx">check</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="s2">&#34;ruff check src tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="s2">&#34;ruff format --check src tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nx">fix</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="s2">&#34;ruff check --fix src tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="s2">&#34;ruff format src tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">typing</span> <span class="p">=</span> <span class="s2">&#34;mypy src&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;check&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c"># 文件環境</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">docs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="s2">&#34;mkdocs&gt;=1.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="s2">&#34;mkdocs-material&gt;=9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">docs</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="nx">build</span> <span class="p">=</span> <span class="s2">&#34;mkdocs build&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nx">serve</span> <span class="p">=</span> <span class="s2">&#34;mkdocs serve&#34;</span></span></span></code></pre></div><h4 id="使用環境">使用環境</h4>





<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"><span class="c1"># 顯示所有環境</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">hatch env show
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 執行預設環境的腳本</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">hatch run <span class="nb">test</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">hatch run test-cov
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 執行特定環境的腳本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">hatch run lint:check
</span></span><span class="line"><span class="ln">10</span><span class="cl">hatch run lint:fix
</span></span><span class="line"><span class="ln">11</span><span class="cl">hatch run lint:typing
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 進入環境 shell</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">hatch shell
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 進入特定環境</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">hatch shell lint
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 移除環境</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">hatch env remove
</span></span><span class="line"><span class="ln">21</span><span class="cl">hatch env remove lint
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># 清除所有環境</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">hatch env prune</span></span></code></pre></div><h3 id="第四步版本管理hatch-version">第四步：版本管理（hatch version）</h3>
<h4 id="設定版本來源">設定版本來源</h4>
<h5 id="方法-a從檔案讀取版本">方法 A：從檔案讀取版本</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/my_awesome_lib/__about__.py&#34;</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># src/my_awesome_lib/__about__.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.1.0&#34;</span></span></span></code></pre></div><h5 id="方法-b從-git-標籤讀取版本推薦用於開源專案">方法 B：從 Git 標籤讀取版本（推薦用於開源專案）</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">,</span> <span class="s2">&#34;hatch-vcs&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;vcs&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">vcs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nx">version-file</span> <span class="p">=</span> <span class="s2">&#34;src/my_awesome_lib/_version.py&#34;</span></span></span></code></pre></div><h4 id="版本操作">版本操作</h4>





<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"><span class="c1"># 顯示當前版本</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">hatch version
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 設定特定版本</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">hatch version 1.0.0
</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"># 語意化版本升級</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">hatch version patch   <span class="c1"># 0.1.0 → 0.1.1</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">hatch version minor   <span class="c1"># 0.1.1 → 0.2.0</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">hatch version major   <span class="c1"># 0.2.0 → 1.0.0</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 預發布版本</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">hatch version alpha   <span class="c1"># 1.0.0 → 1.0.1a0</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">hatch version beta    <span class="c1"># 1.0.1a0 → 1.0.1b0</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">hatch version rc      <span class="c1"># 1.0.1b0 → 1.0.1rc0</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">hatch version release <span class="c1"># 1.0.1rc0 → 1.0.1</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 開發版本</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">hatch version dev     <span class="c1"># 1.0.0 → 1.0.1.dev0</span></span></span></code></pre></div><h3 id="第五步建構與發布">第五步：建構與發布</h3>
<h4 id="建構套件">建構套件</h4>





<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"><span class="c1"># 建構 wheel 和 sdist</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">hatch build
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 只建構 wheel</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">hatch build --target wheel
</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"># 只建構 sdist</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">hatch build --target sdist
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 清除建構產物後重建</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">hatch build --clean</span></span></code></pre></div><h5 id="建構產物">建構產物</h5>





<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">dist/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── my_awesome_lib-0.1.0-py3-none-any.whl
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── my_awesome_lib-0.1.0.tar.gz</span></span></code></pre></div><h4 id="發布套件">發布套件</h4>





<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"><span class="c1"># 發布到 PyPI（需要設定認證）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hatch publish
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 發布到 TestPyPI</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">hatch publish --repo <span class="nb">test</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"># 指定發布的檔案</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">hatch publish dist/my_awesome_lib-0.1.0-py3-none-any.whl</span></span></code></pre></div><h5 id="設定-pypi-認證">設定 PyPI 認證</h5>





<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"><span class="c1"># 設定 PyPI token</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hatch config <span class="nb">set</span> pypi.auth.username __token__
</span></span><span class="line"><span class="ln">3</span><span class="cl">hatch config <span class="nb">set</span> pypi.auth.password pypi-xxxxx
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 或使用環境變數</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">export</span> <span class="nv">HATCH_INDEX_USER</span><span class="o">=</span>__token__
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nb">export</span> <span class="nv">HATCH_INDEX_AUTH</span><span class="o">=</span>pypi-xxxxx</span></span></code></pre></div><h2 id="pyprojecttoml-的-hatch-特定設定">pyproject.toml 的 Hatch 特定設定</h2>
<h3 id="toolhatchbuild-建構設定">[tool.hatch.build] 建構設定</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># 包含的檔案（支援 glob）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">include</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="s2">&#34;src/my_awesome_lib&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="s2">&#34;README.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c"># 排除的檔案</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">exclude</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="s2">&#34;*.pyc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="s2">&#34;__pycache__&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="s2">&#34;.git&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c"># 是否可重現建構</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">reproducible</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c"># 開發模式設定</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">dev-mode-dirs</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">sdist</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c"># 原始碼發布設定</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">include</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="s2">&#34;/src&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="s2">&#34;/tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="s2">&#34;/README.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="s2">&#34;/LICENSE.txt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c"># Wheel 發布設定</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/my_awesome_lib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c"># 只包含特定平台</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="c"># only-include = [&#34;my_awesome_lib&#34;]</span></span></span></code></pre></div><h3 id="toolhatchenvs-環境設定">[tool.hatch.envs] 環境設定</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># 相依性</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c"># 額外安裝的 features</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;yaml&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c"># 環境變數</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">env-vars</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">PYTHONPATH</span> <span class="p">=</span> <span class="s2">&#34;src&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">LOG_LEVEL</span> <span class="p">=</span> <span class="s2">&#34;DEBUG&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c"># 腳本定義</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">test</span> <span class="p">=</span> <span class="s2">&#34;pytest {args}&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c"># 平台特定設定</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">overrides</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">platform</span><span class="p">.</span><span class="nx">windows</span><span class="p">.</span><span class="nx">scripts</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="s1">&#39;test = &#34;pytest --no-header {args}&#34;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><h3 id="toolhatchversion-版本設定">[tool.hatch.version] 版本設定</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 從檔案讀取</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/my_awesome_lib/__about__.py&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">pattern</span> <span class="p">=</span> <span class="s2">&#34;^__version__ = [&#39;\&#34;](/python-advanced/07-packaging/case-studies/hatch-workflow/?P&lt;version&gt;[^&#39;\&#34;]+)[&#39;\&#34;]&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c"># 從 VCS 讀取</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;vcs&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">raw-options</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">local_scheme</span> <span class="p">=</span> <span class="s2">&#34;no-local-version&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">vcs</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">version-file</span> <span class="p">=</span> <span class="s2">&#34;src/my_awesome_lib/_version.py&#34;</span></span></span></code></pre></div><h3 id="toolhatchmetadata-元資料設定">[tool.hatch.metadata] 元資料設定</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">metadata</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># 允許直接依賴（通常應該避免）</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">allow-direct-references</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c"># 動態讀取 README</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">fancy-pypi-readme</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">content-type</span> <span class="p">=</span> <span class="s2">&#34;text/markdown&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">[[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">fancy-pypi-readme</span><span class="p">.</span><span class="nx">fragments</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span></span></span></code></pre></div><h2 id="與-poetry-的比較">與 Poetry 的比較</h2>
<h3 id="設計理念差異">設計理念差異</h3>





<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">Hatch：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── 遵循 PEP 標準優先
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── 環境管理內建
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 不提供依賴鎖定
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── 建構系統（hatchling）可獨立使用
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">└── 設定完全在 [tool.hatch]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Poetry：
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── 自有生態系統
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 強調依賴鎖定（poetry.lock）
</span></span><span class="line"><span class="ln">11</span><span class="cl">├── 虛擬環境管理
</span></span><span class="line"><span class="ln">12</span><span class="cl">├── poetry.core 可獨立使用
</span></span><span class="line"><span class="ln">13</span><span class="cl">└── 混合 [project] 和 [tool.poetry]</span></span></code></pre></div><h3 id="功能對照">功能對照</h3>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>Hatch</th>
          <th>Poetry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>依賴鎖定</td>
          <td>不支援</td>
          <td>poetry.lock</td>
      </tr>
      <tr>
          <td>環境管理</td>
          <td>內建矩陣支援</td>
          <td>單一環境</td>
      </tr>
      <tr>
          <td>PEP 621</td>
          <td>完全支援</td>
          <td>Poetry 2.0 支援</td>
      </tr>
      <tr>
          <td>腳本系統</td>
          <td>強大（環境分離）</td>
          <td>基本</td>
      </tr>
      <tr>
          <td>版本管理</td>
          <td>內建 bump</td>
          <td>需外掛或手動</td>
      </tr>
      <tr>
          <td>插件系統</td>
          <td>支援</td>
          <td>支援</td>
      </tr>
  </tbody>
</table>
<h3 id="pyprojecttoml-比較">pyproject.toml 比較</h3>
<h4 id="hatch-風格">Hatch 風格</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-package&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;requests&gt;=2.28&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">dev</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">features</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;dev&#34;</span><span class="p">]</span></span></span></code></pre></div><h4 id="poetry-風格20">Poetry 風格（2.0）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;poetry-core&gt;=2.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;poetry.core.masonry.api&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-package&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;requests&gt;=2.28&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">poetry</span><span class="p">.</span><span class="nx">group</span><span class="p">.</span><span class="nx">dev</span><span class="p">.</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">pytest</span> <span class="p">=</span> <span class="s2">&#34;^8.0&#34;</span></span></span></code></pre></div><h3 id="選擇建議">選擇建議</h3>





<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">選擇 Hatch：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── 開發 Python 函式庫
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── 需要多環境測試
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 偏好標準優先
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">└── 不需要依賴鎖定
</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">選擇 Poetry：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── 開發應用程式
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── 需要嚴格的依賴鎖定
</span></span><span class="line"><span class="ln">10</span><span class="cl">├── 團隊習慣 Poetry 工作流
</span></span><span class="line"><span class="ln">11</span><span class="cl">└── 需要與現有 Poetry 專案整合</span></span></code></pre></div><h2 id="實用技巧">實用技巧</h2>
<h3 id="多環境測試矩陣">多環境測試矩陣</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 定義多 Python 版本測試</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">test</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest&#34;</span><span class="p">,</span> <span class="s2">&#34;pytest-cov&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">[[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">test</span><span class="p">.</span><span class="nx">matrix</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">python</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;3.9&#34;</span><span class="p">,</span> <span class="s2">&#34;3.10&#34;</span><span class="p">,</span> <span class="s2">&#34;3.11&#34;</span><span class="p">,</span> <span class="s2">&#34;3.12&#34;</span><span class="p">,</span> <span class="s2">&#34;3.13&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">test</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;pytest {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">cov</span> <span class="p">=</span> <span class="s2">&#34;pytest --cov=my_awesome_lib {args:tests}&#34;</span></span></span></code></pre></div>




<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"><span class="c1"># 在所有矩陣環境執行測試</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">hatch run test:run
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 在特定版本執行</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">hatch run +py<span class="o">=</span>3.12 test:run
</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"># 顯示矩陣環境</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">hatch env show --ascii</span></span></code></pre></div><h3 id="複合腳本">複合腳本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c"># 單一命令</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">test</span> <span class="p">=</span> <span class="s2">&#34;pytest {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c"># 多命令（依序執行）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">ci</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="s2">&#34;ruff check src tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="s2">&#34;pytest --cov&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="s2">&#34;mypy src&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># 呼叫其他腳本</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;lint:check&#34;</span><span class="p">,</span> <span class="s2">&#34;test:cov&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c"># 帶預設參數</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">lint</span> <span class="p">=</span> <span class="s2">&#34;ruff check {args:.}&#34;</span></span></span></code></pre></div><h3 id="環境繼承">環境繼承</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># 基礎環境</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">base</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c"># 繼承基礎環境</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">coverage</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">template</span> <span class="p">=</span> <span class="s2">&#34;base&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest-cov&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">coverage</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">run</span> <span class="p">=</span> <span class="s2">&#34;pytest --cov {args:tests}&#34;</span></span></span></code></pre></div><h3 id="條件依賴">條件依賴</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="s2">&#34;pytest&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c"># 根據平台新增依賴</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">overrides</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">platform</span><span class="p">.</span><span class="nx">linux</span><span class="p">.</span><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest-xdist&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">platform</span><span class="p">.</span><span class="nx">darwin</span><span class="p">.</span><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;pytest-xdist&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c"># 根據 Python 版本</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">python</span><span class="p">.</span><span class="mf">3.8</span><span class="p">.</span><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;typing-extensions&#34;</span><span class="p">]</span></span></span></code></pre></div><h3 id="自訂建構-hook">自訂建構 Hook</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">hooks</span><span class="p">.</span><span class="nx">custom</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c"># 建構前執行</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;hatch_build.py&#34;</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># hatch_build.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">hatchling.builders.hooks.plugin.interface</span> <span class="kn">import</span> <span class="n">BuildHookInterface</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">class</span> <span class="nc">CustomBuildHook</span><span class="p">(</span><span class="n">BuildHookInterface</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">version</span><span class="p">,</span> <span class="n">build_data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="c1"># 建構前的自訂邏輯</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Building version </span><span class="si">{</span><span class="n">version</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="完整範例cli-應用程式">完整範例：CLI 應用程式</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># pyproject.toml</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">build-system</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;hatchling&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">build-backend</span> <span class="p">=</span> <span class="s2">&#34;hatchling.build&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-cli&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">dynamic</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;version&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;A useful CLI tool&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">readme</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">requires-python</span> <span class="p">=</span> <span class="s2">&#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">license</span> <span class="p">=</span> <span class="s2">&#34;MIT&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="s2">&#34;click&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="s2">&#34;rich&gt;=13.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nx">my-cli</span> <span class="p">=</span> <span class="s2">&#34;my_cli.main:app&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">optional-dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nx">yaml</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;PyYAML&gt;=6.0&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">version</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;src/my_cli/__about__.py&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">build</span><span class="p">.</span><span class="nx">targets</span><span class="p">.</span><span class="nx">wheel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="nx">packages</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src/my_cli&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c"># ===== 環境設定 =====</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="s2">&#34;pytest&gt;=8.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="s2">&#34;pytest-cov&gt;=4.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="nx">test</span> <span class="p">=</span> <span class="s2">&#34;pytest {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="nx">cov</span> <span class="p">=</span> <span class="s2">&#34;pytest --cov=my_cli --cov-report=term-missing {args:tests}&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">lint</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">  <span class="s2">&#34;ruff&gt;=0.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="s2">&#34;mypy&gt;=1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">lint</span><span class="p">.</span><span class="nx">scripts</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="nx">check</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;ruff check src tests&#34;</span><span class="p">,</span> <span class="s2">&#34;ruff format --check src tests&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="nx">fix</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;ruff check --fix src tests&#34;</span><span class="p">,</span> <span class="s2">&#34;ruff format src tests&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="nx">typing</span> <span class="p">=</span> <span class="s2">&#34;mypy src&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="nx">all</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;check&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="p">[[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">hatch</span><span class="p">.</span><span class="nx">envs</span><span class="p">.</span><span class="nx">test</span><span class="p">.</span><span class="nx">matrix</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="nx">python</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;3.10&#34;</span><span class="p">,</span> <span class="s2">&#34;3.11&#34;</span><span class="p">,</span> <span class="s2">&#34;3.12&#34;</span><span class="p">,</span> <span class="s2">&#34;3.13&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="c"># ===== 工具設定 =====</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="nx">src</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;src&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="nx">line-length</span> <span class="p">=</span> <span class="mi">88</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">ruff</span><span class="p">.</span><span class="nx">lint</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="nx">select</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;E&#34;</span><span class="p">,</span> <span class="s2">&#34;W&#34;</span><span class="p">,</span> <span class="s2">&#34;F&#34;</span><span class="p">,</span> <span class="s2">&#34;I&#34;</span><span class="p">,</span> <span class="s2">&#34;B&#34;</span><span class="p">,</span> <span class="s2">&#34;UP&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">mypy</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="nx">python_version</span> <span class="p">=</span> <span class="s2">&#34;3.10&#34;</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="nx">strict</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="p">[</span><span class="nx">tool</span><span class="p">.</span><span class="nx">pytest</span><span class="p">.</span><span class="nx">ini_options</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="nx">testpaths</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;tests&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="nx">addopts</span> <span class="p">=</span> <span class="s2">&#34;-v&#34;</span></span></span></code></pre></div><h2 id="發布檢查清單">發布檢查清單</h2>





<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">發布前檢查：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── [ ] hatch run lint:all 通過
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── [ ] hatch run test:run 在所有 Python 版本通過
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── [ ] hatch version 更新版本號
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">├── [ ] 更新 CHANGELOG.md
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── [ ] hatch build 建構成功
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">├── [ ] 在虛擬環境測試安裝：pip install dist/*.whl
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">├── [ ] hatch publish --repo test 發布到 TestPyPI
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">├── [ ] 從 TestPyPI 測試安裝
</span></span><span class="line"><span class="ln">10</span><span class="cl">└── [ ] hatch publish 發布到 PyPI</span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://hatch.pypa.io/">Hatch 官方文件</a></li>
<li><a href="https://hatch.pypa.io/latest/plugins/build-hook/reference/">Hatchling 建構後端</a></li>
<li><a href="https://peps.python.org/pep-0621/">PEP 621 - pyproject.toml 元資料</a></li>
<li><a href="https://packaging.python.org/">Python 打包使用者指南</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/07-packaging/case-studies/" data-link-title="案例研究" data-link-desc="基於 .claude/lib 實際程式碼的打包發布案例">案例研究</a></p>
]]></content:encoded></item><item><title>案例：異常設計架構</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_io.py&lt;/code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib/hook_io.py&lt;/code> 使用簡單的錯誤處理方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_hook_input&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 從 stdin 讀取 Hook 輸入
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict: 解析後的 JSON 資料，解析失敗時返回空字典
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">try&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="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&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="k">except&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&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="k">return&lt;/span> &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="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個設計有以下特點：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>捕獲所有異常&lt;/strong>：使用 &lt;code>except Exception&lt;/code> 確保不會崩潰&lt;/li>
&lt;li>&lt;strong>靜默失敗&lt;/strong>：錯誤時返回空字典，不報告錯誤原因&lt;/li>
&lt;li>&lt;strong>無法區分錯誤類型&lt;/strong>：JSON 解析錯誤和其他錯誤用同樣方式處理&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單可靠&lt;/strong>：Hook 不會因為輸入問題而崩潰&lt;/li>
&lt;li>&lt;strong>使用標準異常&lt;/strong>：不需要定義額外類別&lt;/li>
&lt;li>&lt;strong>API 簡潔&lt;/strong>：呼叫者不需要處理異常&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當錯誤處理變複雜時，這個設計會遇到問題：&lt;/p>
&lt;h4 id="問題-1無法區分不同來源的錯誤">問題 1：無法區分不同來源的錯誤&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_hook&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">input_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&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="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">input_data&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="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 無法給出有意義的錯誤訊息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;unknown error&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2多個錯誤只能報告第一個">問題 2：多個錯誤只能報告第一個&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&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="s2">&amp;#34;&amp;#34;&amp;#34;驗證 Hook 配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_name&amp;#34;&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">5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_input&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&lt;/span>&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 class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_input&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 永遠不會執行到&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3錯誤恢復邏輯難以實作">問題 3：錯誤恢復邏輯難以實作&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">handle_hook_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;處理 Hook 錯誤&amp;#34;&amp;#34;&amp;#34;&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"># 問題：只能用 isinstance 檢查，很難擴展&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;invalid json&amp;#34;&lt;/span>&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 class="k">elif&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;file not found&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="k">else&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">)}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立清晰的異常階層&lt;/strong>：不同錯誤類型有不同的異常類別&lt;/li>
&lt;li>&lt;strong>支援多重錯誤收集&lt;/strong>：使用 ExceptionGroup 收集多個驗證錯誤&lt;/li>
&lt;li>&lt;strong>提供豐富的錯誤資訊&lt;/strong>：異常攜帶足夠的上下文資訊&lt;/li>
&lt;li>&lt;strong>支援錯誤恢復策略&lt;/strong>：可以根據錯誤類型決定恢復方式&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1設計異常階層">步驟 1：設計異常階層&lt;/h4>
&lt;p>設計異常階層時，要考慮錯誤的分類和繼承關係：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">Hook 異常階層設計
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">Exception hierarchy:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> HookError (base)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── HookConfigError (configuration issues)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── ConfigNotFoundError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── ConfigParseError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ └── ConfigValidationError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── HookInputError (input processing)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ ├── InputReadError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">&lt;span class="s2"> │ └── InputValidationError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="s2"> └── HookExecutionError (runtime issues)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="s2"> ├── ToolNotFoundError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="s2"> └── PermissionDeniedError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">Exception&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Hook 異常基礎類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl">&lt;span class="s2"> 所有 Hook 相關的異常都繼承自這個類別，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 讓呼叫者可以用 `except HookError` 捕獲所有 Hook 錯誤。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__str__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl"> &lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Configuration Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfigError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置相關錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置檔案不存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">search_paths&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">search_paths&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;search_paths&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_paths&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigParseError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置檔案解析失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">detail&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">detail&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">detail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to parse config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; at line &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">line&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">detail&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">detail&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置內容驗證失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">reason&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Input Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookInputError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;輸入相關錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">InputReadError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;讀取輸入失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cause&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">source&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cause&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to read from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">source&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">cause&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__cause__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">InputValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;輸入驗證失敗&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">actual&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">expected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">actual&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">actual&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid input: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">expected&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">actual&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">actual&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;expected&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Execution Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookExecutionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;執行時錯誤的基礎類別&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ToolNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;工具不存在&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tool_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">available_tools&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tool_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">available_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">available_tools&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Tool &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">tool_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">tool_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;available&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">available_tools&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PermissionDeniedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;權限不足&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">resource&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">action&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">action&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Permission denied: cannot &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">action&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">reason&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;resource&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">resource&lt;/span>&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2豐富的錯誤資訊">步驟 2：豐富的錯誤資訊&lt;/h4>
&lt;p>異常類別可以攜帶豐富的上下文資訊，幫助除錯和錯誤恢復：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_io.py</code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib/hook_io.py</code> 使用簡單的錯誤處理方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    從 stdin 讀取 Hook 輸入
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        dict: 解析後的 JSON 資料，解析失敗時返回空字典
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><p>這個設計有以下特點：</p>
<ol>
<li><strong>捕獲所有異常</strong>：使用 <code>except Exception</code> 確保不會崩潰</li>
<li><strong>靜默失敗</strong>：錯誤時返回空字典，不報告錯誤原因</li>
<li><strong>無法區分錯誤類型</strong>：JSON 解析錯誤和其他錯誤用同樣方式處理</li>
</ol>
<h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單可靠</strong>：Hook 不會因為輸入問題而崩潰</li>
<li><strong>使用標準異常</strong>：不需要定義額外類別</li>
<li><strong>API 簡潔</strong>：呼叫者不需要處理異常</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當錯誤處理變複雜時，這個設計會遇到問題：</p>
<h4 id="問題-1無法區分不同來源的錯誤">問題 1：無法區分不同來源的錯誤</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">input_data</span> <span class="o">=</span> <span class="n">read_hook_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">input_data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="c1"># 無法給出有意義的錯誤訊息</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;unknown error&#34;</span><span class="p">}</span></span></span></code></pre></div><h4 id="問題-2多個錯誤只能報告第一個">問題 2：多個錯誤只能報告第一個</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_config</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證 Hook 配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_name&#34;</span><span class="p">)</span>  <span class="c1"># 第一個錯誤後就停止</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_input&#34;</span><span class="p">)</span>  <span class="c1"># 永遠不會執行到</span></span></span></code></pre></div><h4 id="問題-3錯誤恢復邏輯難以實作">問題 3：錯誤恢復邏輯難以實作</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">handle_hook_error</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理 Hook 錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># 問題：只能用 isinstance 檢查，很難擴展</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;invalid json&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="ne">FileNotFoundError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;file not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">)}</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立清晰的異常階層</strong>：不同錯誤類型有不同的異常類別</li>
<li><strong>支援多重錯誤收集</strong>：使用 ExceptionGroup 收集多個驗證錯誤</li>
<li><strong>提供豐富的錯誤資訊</strong>：異常攜帶足夠的上下文資訊</li>
<li><strong>支援錯誤恢復策略</strong>：可以根據錯誤類型決定恢復方式</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1設計異常階層">步驟 1：設計異常階層</h4>
<p>設計異常階層時，要考慮錯誤的分類和繼承關係：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Hook 異常階層設計
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">Exception hierarchy:
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    HookError (base)
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    ├── HookConfigError (configuration issues)
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    │   ├── ConfigNotFoundError
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    │   ├── ConfigParseError
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    │   └── ConfigValidationError
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    ├── HookInputError (input processing)
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    │   ├── InputReadError
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    │   └── InputValidationError
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    └── HookExecutionError (runtime issues)
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        ├── ToolNotFoundError
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">        └── PermissionDeniedError
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    Hook 異常基礎類別
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    所有 Hook 相關的異常都繼承自這個類別，
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    讓呼叫者可以用 `except HookError` 捕獲所有 Hook 錯誤。
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1"># === Configuration Errors ===</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置相關錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案不存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案解析失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigValidationError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置內容驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Invalid config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="c1"># === Input Errors ===</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="k">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="s2">&#34;&#34;&#34;輸入相關錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="s2">&#34;&#34;&#34;讀取輸入失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="k">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="s2">&#34;&#34;&#34;輸入驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="c1"># === Execution Errors ===</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行時錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="k">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="s2">&#34;&#34;&#34;工具不存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="s2">&#34;&#34;&#34;權限不足&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</span><span class="p">})</span></span></span></code></pre></div><h4 id="步驟-2豐富的錯誤資訊">步驟 2：豐富的錯誤資訊</h4>
<p>異常類別可以攜帶豐富的上下文資訊，幫助除錯和錯誤恢復：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    錯誤上下文資訊
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    收集錯誤發生時的環境資訊，
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    幫助除錯和錯誤報告。
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">timestamp</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">hook_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">stack_info</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為字典，方便序列化&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">RichHookError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    帶有豐富上下文的 Hook 異常
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    提供詳細的錯誤資訊，包含：
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    - 錯誤發生的時間
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    - 相關的 Hook 和工具資訊
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    - 環境變數
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">    - 建議的修復方式
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">error_code</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">suggestions</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">recoverable</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span> <span class="o">=</span> <span class="n">error_code</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span> <span class="o">=</span> <span class="n">error_context</span> <span class="ow">or</span> <span class="n">ErrorContext</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span> <span class="o">=</span> <span class="n">suggestions</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span> <span class="o">=</span> <span class="n">recoverable</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="nf">format_message</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;格式化完整錯誤訊息&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Hook: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Tool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Suggestions:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">            <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">suggestion</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">. </span><span class="si">{</span><span class="n">suggestion</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="nf">to_response</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為 Hook 回應格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="s2">&#34;error_code&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="s2">&#34;suggestions&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="s2">&#34;recoverable&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="c1"># Example: specific error with rich context</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidationError</span><span class="p">(</span><span class="n">RichHookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">    Hook 驗證錯誤
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="s2">    當 Hook 輸入驗證失敗時拋出，
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">    提供詳細的錯誤資訊和修復建議。
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="n">actual_value</span><span class="p">:</span> <span class="n">Any</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual_value</span> <span class="o">=</span> <span class="n">actual_value</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Validation failed for &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">suggestions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_suggestions</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">reason</span><span class="p">,</span> <span class="n">actual_value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">            <span class="n">message</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="n">error_code</span><span class="o">=</span><span class="s2">&#34;HOOK_VALIDATION_ERROR&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">error_context</span><span class="o">=</span><span class="n">error_context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">suggestions</span><span class="o">=</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">recoverable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">_generate_suggestions</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">actual_value</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據錯誤類型生成修復建議&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="n">suggestions</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;required&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Add the required field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; to your input&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;type&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check the type of &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; - expected type may differ&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="k">if</span> <span class="n">actual_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Current value: </span><span class="si">{</span><span class="n">actual_value</span><span class="si">!r}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">return</span> <span class="n">suggestions</span></span></span></code></pre></div><h4 id="步驟-3使用-exceptiongrouppython-311">步驟 3：使用 ExceptionGroup（Python 3.11+）</h4>
<p>Python 3.11 引入的 <code>ExceptionGroup</code> 可以同時報告多個錯誤：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCollector</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    驗證錯誤收集器
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    收集多個驗證錯誤，最後用 ExceptionGroup 一次報告。
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    這樣可以讓使用者一次看到所有錯誤，而不是修完一個才看到下一個。
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context_name</span> <span class="o">=</span> <span class="n">context_name</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增一個錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">        檢查條件，失敗時收集錯誤
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">            condition: 要檢查的條件
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">            error: 條件為 False 時要收集的錯誤
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;是否有錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">raise_if_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        如果有錯誤，拋出 ExceptionGroup
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">            ExceptionGroup: 包含所有收集到的錯誤
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">errors</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">    驗證 Hook 輸入，收集所有錯誤
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">    使用 ExceptionGroup 一次報告所有驗證錯誤，
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">    而不是只報告第一個錯誤。
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        data: 待驗證的輸入資料
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">        驗證後的資料
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">    Raises:
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">        ExceptionGroup: 包含所有驗證錯誤
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="c1"># Check required fields</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="c1"># Check types (only if fields exist)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="c1"># Check tool-specific validation</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">if</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;Write&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="s2">&#34;file_path&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.file_path&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="s2">&#34;content&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.content&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="c1"># Raise all errors at once</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"># Using except* to handle ExceptionGroup (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">handle_validation_errors_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範如何用 except* 處理 ExceptionGroup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span>  <span class="c1"># Wrong type</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="c1"># Missing tool_input</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="c1"># Handle all InputValidationError instances</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> validation errors:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="c1"># Handle other HookError instances</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> hook errors&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4錯誤恢復策略">步驟 4：錯誤恢復策略</h4>
<p>設計可以根據錯誤類型決定恢復方式的機制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="k">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</span><span class="p">,</span> <span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    錯誤恢復策略抽象基礎類別
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    定義錯誤恢復的介面，讓不同類型的錯誤
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    可以有不同的恢復方式。
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷是否可以恢復此錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行恢復邏輯，返回恢復後的結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">    預設值恢復策略
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">    當錯誤發生時，返回預設值。
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="k">class</span> <span class="nc">RetryRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    重試恢復策略
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    當錯誤是暫時性的，嘗試重試。
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="n">operation</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">dict</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">recoverable_errors</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">type</span><span class="p">[</span><span class="ne">Exception</span><span class="p">],</span> <span class="o">...</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="ne">IOError</span><span class="p">,)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">operation</span> <span class="o">=</span> <span class="n">operation</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span> <span class="o">=</span> <span class="n">max_retries</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span> <span class="o">=</span> <span class="n">recoverable_errors</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="n">last_error</span> <span class="o">=</span> <span class="n">error</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">**</span> <span class="n">attempt</span><span class="p">))</span>  <span class="c1"># Exponential backoff</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">operation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="k">except</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="n">last_error</span> <span class="o">=</span> <span class="n">e</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="c1"># All retries failed, re-raise the last error</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">raise</span> <span class="n">last_error</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    錯誤恢復鏈
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">    按順序嘗試多個恢復策略，
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    直到找到可以處理的策略。
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增恢復策略&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="s2">        嘗試恢復錯誤
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">            error: 要恢復的錯誤
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">            (是否成功恢復, 恢復結果或 None)
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input_with_recovery</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="s2">    讀取 Hook 輸入，帶有錯誤恢復機制
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="s2">    使用恢復鏈處理不同類型的錯誤。
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="c1"># Set up recovery chain</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="c1"># JSON parsing error - try to recover</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="c1"># Multiple validation errors</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="c1"># Check if all errors are recoverable</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="n">all_recoverable</span> <span class="o">=</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="p">(</span><span class="n">InputValidationError</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">if</span> <span class="n">all_recoverable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;validation_errors&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="c1"># Unknown error</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Hook Exception Hierarchy - Complete Example
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">Demonstrates how to design a clear exception hierarchy
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">and use ExceptionGroup for multiple error handling.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="c1"># ===== Exception Hierarchy =====</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base class for all Hook-related exceptions.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Configuration-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Config file not found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">
</span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to parse config file.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="k">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to read input.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="k">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input validation failed.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Execution-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="k">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Tool not found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Permission denied.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="c1"># ===== Error Context =====</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Rich context information for errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="n">timestamp</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="n">hook_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># ===== Validation Collector =====</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCollector</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Collects validation errors and raises ExceptionGroup.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context_name</span> <span class="o">=</span> <span class="n">context_name</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">def</span> <span class="nf">raise_if_errors</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">errors</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl"><span class="c1"># ===== Recovery Strategies =====</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl"><span class="k">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</span><span class="p">,</span> <span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Abstract base class for error recovery strategies.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="k">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Returns a default value when error occurs.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Chain of recovery strategies.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">
</span></span><span class="line"><span class="ln">192</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl"><span class="c1"># ===== Main Functions =====</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">
</span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">    Validate hook input, collecting all errors.
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">    Uses ExceptionGroup to report all validation errors at once.
</span></span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="c1"># Required fields</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">
</span></span><span class="line"><span class="ln">229</span><span class="cl">    <span class="c1"># Type checks</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input_safe</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="s2">    Read hook input with error recovery.
</span></span></span><span class="line"><span class="ln">256</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">257</span><span class="cl"><span class="s2">    Enhanced version of the original read_hook_input()
</span></span></span><span class="line"><span class="ln">258</span><span class="cl"><span class="s2">    with proper exception handling.
</span></span></span><span class="line"><span class="ln">259</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="c1"># Re-raise validation errors as-is</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">
</span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="k">def</span> <span class="nf">create_error_response</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="n">HookError</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a standardized error response.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">            <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">type</span><span class="p">(</span><span class="n">error</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="n">error</span><span class="o">.</span><span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Exception Hierarchy Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="c1"># Demo 1: Basic exception</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic exception:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;/path/1&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/2&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Context: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">context</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="c1"># Demo 2: Exception chaining</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Exception chaining:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="k">raise</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">(</span><span class="s2">&#34;Unexpected EOF&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">        <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">            <span class="k">raise</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="k">except</span> <span class="n">InputReadError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caused by: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="c1"># Demo 3: ExceptionGroup</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. ExceptionGroup (multiple errors):&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">})</span>  <span class="c1"># Wrong type, missing tool_input</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught ExceptionGroup: </span><span class="si">{</span><span class="n">eg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">err</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Error </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">
</span></span><span class="line"><span class="ln">330</span><span class="cl">    <span class="c1"># Demo 4: except* syntax (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">4. Using except* to handle specific error types:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({})</span>  <span class="c1"># Missing both fields</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> InputValidationError(s)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   - </span><span class="si">{</span><span class="n">err</span><span class="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="c1"># Demo 5: Error recovery</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">5. Error recovery:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;default&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}))</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="n">error</span> <span class="o">=</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;missing_config&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Recovered: </span><span class="si">{</span><span class="n">recovered</span><span class="si">}</span><span class="s2">, Result: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_exceptions</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">HookError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">ConfigNotFoundError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">InputValidationError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">create_error_response</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理 Hook 請求&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># Load configuration</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="c1"># Validate input</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">read_input</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Process...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">except</span> <span class="n">ConfigNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Specific handling for missing config</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Warning: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">, using defaults&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;warning&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">except</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1"># Input validation error</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">except</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="c1"># Catch-all for other hook errors</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></span></span></code></pre></div><h4 id="多重錯誤處理">多重錯誤處理</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_batch_inputs</span><span class="p">(</span><span class="n">inputs</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    驗證多個輸入，收集所有錯誤
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    即使某些輸入失敗，仍然處理其他輸入。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">all_errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">inputs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">validated</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;data&#34;</span><span class="p">:</span> <span class="n">validated</span><span class="p">,</span> <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="c1"># Collect errors from this input</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                <span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="p">})</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">except</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># Report summary</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">if</span> <span class="n">all_errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation completed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> total errors&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># Using except* to handle different error types differently</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">handle_mixed_errors</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範用 except* 分別處理不同類型的錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="c1"># This might raise ExceptionGroup with mixed errors</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">process_complex_operation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="c1"># Handle validation errors - maybe log and continue</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">log_validation_error</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">PermissionDeniedError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="c1"># Handle permission errors - need to notify admin</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="n">notify_admin</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">HookExecutionError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="c1"># Handle execution errors - maybe retry</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">schedule_retry</span><span class="p">(</span><span class="n">err</span><span class="p">)</span></span></span></code></pre></div><h4 id="異常鏈exception-chaining">異常鏈（Exception Chaining）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">load_config_with_chain</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    載入配置，保留原始錯誤資訊
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用 `raise ... from ...` 保留異常鏈，
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    讓除錯時可以看到完整的錯誤來源。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">config_path</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;/config/</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Wrap in domain-specific exception, preserve original</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">[</span><span class="n">config_path</span><span class="p">])</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">except</span> <span class="n">yaml</span><span class="o">.</span><span class="n">YAMLError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Extract line number if available</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">line</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="s2">&#34;problem_mark&#34;</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">line_num</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">line</span> <span class="k">if</span> <span class="n">line</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigParseError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">line</span><span class="o">=</span><span class="n">line_num</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">except</span> <span class="ne">PermissionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Convert to domain exception</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">raise</span> <span class="n">PermissionDeniedError</span><span class="p">(</span><span class="s2">&#34;read&#34;</span><span class="p">,</span> <span class="n">config_path</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># When catching, you can access the full chain</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">def</span> <span class="nf">debug_config_error</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">load_config_with_chain</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Original cause: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="c1"># Print full traceback including cause</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="kn">import</span> <span class="nn">traceback</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">traceback</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">e</span><span class="p">,</span> <span class="n">e</span><span class="o">.</span><span class="n">__traceback__</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>標準異常</th>
          <th>自定義階層</th>
          <th>ExceptionGroup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>學習成本</td>
          <td>低</td>
          <td>中</td>
          <td>中高</td>
      </tr>
      <tr>
          <td>錯誤辨識</td>
          <td>困難</td>
          <td>清晰</td>
          <td>清晰</td>
      </tr>
      <tr>
          <td>錯誤資訊</td>
          <td>有限</td>
          <td>豐富</td>
          <td>豐富</td>
      </tr>
      <tr>
          <td>多重錯誤</td>
          <td>只報一個</td>
          <td>只報一個</td>
          <td>全部報告</td>
      </tr>
      <tr>
          <td>恢復策略</td>
          <td>難實作</td>
          <td>易實作</td>
          <td>可分類處理</td>
      </tr>
      <tr>
          <td>Python 版本</td>
          <td>所有版本</td>
          <td>所有版本</td>
          <td>3.11+</td>
      </tr>
      <tr>
          <td>程式碼量</td>
          <td>最少</td>
          <td>中等</td>
          <td>較多</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用哪種方案">何時使用哪種方案？</h3>
<h4 id="使用標準異常如-hook_iopy-的做法">使用標準異常（如 <code>hook_io.py</code> 的做法）</h4>
<ul>
<li>簡單的腳本或工具</li>
<li>錯誤處理很簡單</li>
<li>不需要區分錯誤類型</li>
</ul>
<h4 id="使用自定義階層">使用自定義階層</h4>
<ul>
<li>函式庫或框架</li>
<li>需要區分不同錯誤類型</li>
<li>需要提供錯誤恢復建議</li>
</ul>
<h4 id="使用-exceptiongroup">使用 ExceptionGroup</h4>
<ul>
<li>批次處理需要收集所有錯誤</li>
<li>驗證邏輯有多個檢查點</li>
<li>需要讓使用者一次看到所有問題</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要區分不同錯誤類型的函式庫</li>
<li>批次處理需要收集所有錯誤</li>
<li>需要提供錯誤恢復建議</li>
<li>需要保留完整的錯誤來源（異常鏈）</li>
<li>Python 3.11+ 環境</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的腳本</li>
<li>錯誤類型很少（&lt; 3 種）</li>
<li>不需要精細的錯誤處理</li>
<li>需要支援舊版 Python（&lt; 3.11）使用 ExceptionGroup</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>設計異常階層</strong>：為一個配置載入模組設計異常階層，包含：
<ul>
<li>檔案不存在</li>
<li>格式錯誤</li>
<li>欄位缺失</li>
<li>型別錯誤</li>
</ul>
</li>
</ol>
<p>提示：先畫出階層圖，再實作類別。</p>
<h3 id="進階練習">進階練習</h3>
<ol>
<li><strong>實作 ExceptionGroup 驗證器</strong>：寫一個表單驗證器，可以：
<ul>
<li>收集所有欄位的驗證錯誤</li>
<li>用 ExceptionGroup 一次報告</li>
<li>支援 <code>except*</code> 分類處理</li>
</ul>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">validate_form</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證表單，收集所有錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;form&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;must be at least 3 characters&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;@&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;must contain @&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">data</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<ol>
<li><strong>實作帶有自動修復建議的異常</strong>：設計一個異常系統，可以：
<ul>
<li>根據錯誤類型自動生成修復建議</li>
<li>支援結構化的錯誤報告（JSON 格式）</li>
<li>提供「可能的修復」和「參考文件」連結</li>
</ul>
</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">validate_config</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">except</span> <span class="n">ConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">format_message</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># [CONFIG_001] Invalid config &#39;agents.yaml&#39;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">#   Field: known_agents</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">#   Error: expected list, got string</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1">#</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># Suggestions:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1">#   1. Change &#39;known_agents: &#34;basil&#34;&#39; to &#39;known_agents: [&#34;basil&#34;]&#39;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">#   2. See: https://docs.example.com/config#known_agents</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0654/">PEP 654 - Exception Groups and except*</a></li>
<li><a href="https://peps.python.org/pep-3134/">PEP 3134 - Exception Chaining</a></li>
<li><a href="https://docs.python.org/3/tutorial/errors.html">Python 異常最佳實踐</a></li>
<li><a href="https://realpython.com/python311-exception-groups/">Real Python - Exception Groups</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></em></p>
]]></content:encoded></item><item><title>案例：類似 Django Field 的設計</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_io.py&lt;/code> 的實際程式碼，展示如何結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_io.py&lt;/code> 使用函式工廠模式建構 Hook 輸出：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_pretooluse_output&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">decision&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">user_prompt&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&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="n">system_message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&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 class="n">suppress_output&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立 PreToolUse Hook 輸出格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> decision: 決策結果 (&amp;#34;allow&amp;#34; | &amp;#34;deny&amp;#34; | &amp;#34;ask&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> reason: 決策原因說明
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> user_prompt: 詢問用戶的訊息（僅當 decision 為 &amp;#34;ask&amp;#34; 時使用）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> system_message: 系統訊息（可選）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> suppress_output: 是否抑制輸出（預設 False）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> dict: 標準 PreToolUse Hook 輸出格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&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">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;hookEventName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;permissionDecision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">decision&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;permissionDecisionReason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">user_prompt&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;userPrompt&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">user_prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">system_message&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;systemMessage&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">system_message&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">suppress_output&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;suppressOutput&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>清晰的建構流程&lt;/strong>：函式簽名清楚說明所需參數&lt;/li>
&lt;li>&lt;strong>支援可選參數&lt;/strong>：使用 &lt;code>Optional&lt;/code> 和預設值處理可選欄位&lt;/li>
&lt;li>&lt;strong>型別提示完整&lt;/strong>：有完整的參數型別標註&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要序列化/反序列化時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>需要手動處理每個欄位&lt;/strong>：to_dict 和 from_dict 需要逐一處理&lt;/li>
&lt;li>&lt;strong>欄位定義與驗證分離&lt;/strong>：型別檢查和業務驗證在不同地方&lt;/li>
&lt;li>&lt;strong>難以生成文件或 schema&lt;/strong>：無法自動產生 JSON Schema 或 API 文件&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>欄位定義包含型別、驗證、序列化&lt;/strong>&lt;/li>
&lt;li>&lt;strong>自動生成 &lt;code>__init__&lt;/code>、&lt;code>to_dict&lt;/code>、&lt;code>from_dict&lt;/code>&lt;/strong>&lt;/li>
&lt;li>&lt;strong>支援巢狀結構&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1設計-field-基類">步驟 1：設計 Field 基類&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_type_hints&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&lt;/span>&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 class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> Field Descriptor base class
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> Combines Django&amp;#39;s field declaration style with Python&amp;#39;s
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> descriptor protocol for type-safe, declarative model design.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="o">*&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">default&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">required&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">serialized_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Validation failed&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> default: Default value when not provided
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> required: Whether field is required (default True)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> serialized_name: Name used in serialization (default: attribute name)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> validator: Optional validation function
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: Error message for validation failure
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">default&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">required&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">required&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">serialized_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">serialized_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Set by __set_name__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__set_name__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> Called automatically when the descriptor is assigned to a class attribute.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> This is where we get the attribute name from the class definition,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> eliminating the need to pass it explicitly.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;_field_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Use attribute name as serialized name if not specified&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">serialized_name&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">serialized_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__get__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Return field value or descriptor itself if accessed on class.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span> &lt;span class="c1"># Class access returns descriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__set__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate and set field value.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Handle None for optional fields&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">required&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: This field is required&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Run custom validator if provided&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">serialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Convert Python value to serializable format.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">78&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">deserialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Convert serialized value to Python type.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">80&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>關鍵設計要點&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_io.py</code> 的實際程式碼，展示如何結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_io.py</code> 使用函式工廠模式建構 Hook 輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">create_pretooluse_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">decision</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">user_prompt</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">system_message</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">suppress_output</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    建立 PreToolUse Hook 輸出格式
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        decision: 決策結果 (&#34;allow&#34; | &#34;deny&#34; | &#34;ask&#34;)
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        reason: 決策原因說明
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        user_prompt: 詢問用戶的訊息（僅當 decision 為 &#34;ask&#34; 時使用）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        system_message: 系統訊息（可選）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        suppress_output: 是否抑制輸出（預設 False）
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        dict: 標準 PreToolUse Hook 輸出格式
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">output</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="s2">&#34;hookEventName&#34;</span><span class="p">:</span> <span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="s2">&#34;permissionDecision&#34;</span><span class="p">:</span> <span class="n">decision</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="s2">&#34;permissionDecisionReason&#34;</span><span class="p">:</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">if</span> <span class="n">user_prompt</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">][</span><span class="s2">&#34;userPrompt&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">user_prompt</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">if</span> <span class="n">system_message</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;systemMessage&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">system_message</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">if</span> <span class="n">suppress_output</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;suppressOutput&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">return</span> <span class="n">output</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>清晰的建構流程</strong>：函式簽名清楚說明所需參數</li>
<li><strong>支援可選參數</strong>：使用 <code>Optional</code> 和預設值處理可選欄位</li>
<li><strong>型別提示完整</strong>：有完整的參數型別標註</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要序列化/反序列化時：</p>
<ul>
<li><strong>需要手動處理每個欄位</strong>：to_dict 和 from_dict 需要逐一處理</li>
<li><strong>欄位定義與驗證分離</strong>：型別檢查和業務驗證在不同地方</li>
<li><strong>難以生成文件或 schema</strong>：無法自動產生 JSON Schema 或 API 文件</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>欄位定義包含型別、驗證、序列化</strong></li>
<li><strong>自動生成 <code>__init__</code>、<code>to_dict</code>、<code>from_dict</code></strong></li>
<li><strong>支援巢狀結構</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1設計-field-基類">步驟 1：設計 Field 基類</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">get_type_hints</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">Field</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Field Descriptor base class
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Combines Django&#39;s field declaration style with Python&#39;s
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    descriptor protocol for type-safe, declarative model design.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">default</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">required</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">serialized_name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="n">T</span><span class="p">],</span> <span class="nb">bool</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Validation failed&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">            default: Default value when not provided
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            required: Whether field is required (default True)
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            serialized_name: Name used in serialization (default: attribute name)
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">            validator: Optional validation function
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            error_msg: Error message for validation failure
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="o">=</span> <span class="n">required</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="o">=</span> <span class="n">serialized_name</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="c1"># Set by __set_name__</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">        Called automatically when the descriptor is assigned to a class attribute.
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">        This is where we get the attribute name from the class definition,
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        eliminating the need to pass it explicitly.
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_field_</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="c1"># Use attribute name as serialized name if not specified</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Return field value or descriptor itself if accessed on class.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span>  <span class="c1"># Class access returns descriptor</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate and set field value.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="c1"># Handle None for optional fields</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: This field is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="c1"># Run custom validator if provided</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert Python value to serializable format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert serialized value to Python type.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="k">return</span> <span class="n">value</span></span></span></code></pre></div><p><strong>關鍵設計要點</strong>：</p>
<ol>
<li><strong><code>__set_name__</code></strong> 自動取得屬性名稱，不需要像 Django 早期版本手動傳入</li>
<li><strong><code>serialized_name</code></strong> 支援序列化時使用不同的欄位名稱（如 camelCase）</li>
<li><strong><code>serialize</code>/<code>deserialize</code></strong> 方法供子類覆寫，實現型別轉換</li>
</ol>
<h4 id="步驟-2實作型別特定的-field">步驟 2：實作型別特定的 Field</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="k">class</span> <span class="nc">StringField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;String field with optional pattern validation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">        <span class="n">pattern</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span> <span class="k">if</span> <span class="n">pattern</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected str, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum length is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum length is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Does not match pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">class</span> <span class="nc">IntField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">int</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Integer field with range validation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected int, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum value is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum value is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="k">class</span> <span class="nc">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected bool, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="k">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Field with predefined choices.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">choices_str</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">repr</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Must be one of: </span><span class="si">{</span><span class="n">choices_str</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="k">class</span> <span class="nc">ListField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">list</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="s2">&#34;&#34;&#34;List field with item type validation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">item_field</span><span class="p">:</span> <span class="n">Field</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="c1"># ListField defaults to empty list, not required</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;default&#34;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;required&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">item_field</span> <span class="o">=</span> <span class="n">item_field</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected list, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="c1"># Validate each item using item_field&#39;s validation logic</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="c1"># (simplified - in production you&#39;d want proper item validation)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span> <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Serialize list items.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Deserialize list items.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="k">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with ISO format serialization.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">format</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">T%H:%M:%S&#34;</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">format</span> <span class="o">=</span> <span class="nb">format</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">datetime</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected datetime, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">datetime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert datetime to ISO string.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">datetime</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Parse ISO string to datetime.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span></span></span></code></pre></div><h4 id="步驟-3用-metaclass-處理欄位收集">步驟 3：用 Metaclass 處理欄位收集</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="k">class</span> <span class="nc">ModelMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">    Metaclass for Model classes.
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    Responsibilities:
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    1. Collect all Field descriptors from class definition
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    2. Store field metadata for serialization/deserialization
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    3. Generate __init__ signature from fields
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">bases</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">namespace</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="c1"># Collect fields from this class and parent classes</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="n">fields</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Field</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">        <span class="c1"># Inherit fields from parent Model classes</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s2">&#34;_fields&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">                <span class="n">fields</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_fields</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">        <span class="c1"># Collect fields from current class</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">                <span class="n">fields</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">attr_value</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="c1"># Store fields metadata</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="n">namespace</span><span class="p">[</span><span class="s2">&#34;_fields&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">fields</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">namespace</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">Model</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">ModelMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    Base class for declarative models.
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    Provides:
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    - Automatic __init__ from field definitions
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    - to_dict() for serialization
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    - from_dict() for deserialization
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">_fields</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Field</span><span class="p">]</span>  <span class="c1"># Set by metaclass</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">        Initialize model from keyword arguments.
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s2">        All defined fields can be passed as keyword arguments.
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">        Required fields must be provided unless they have defaults.
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="c1"># Set each field value, triggering descriptor validation</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">field</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="c1"># Check for unknown fields</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="n">unknown</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">if</span> <span class="n">unknown</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown fields: </span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">unknown</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">        Serialize model to dictionary.
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">        Uses each field&#39;s serialized_name and serialize() method.
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">required</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="n">serialized_name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="n">result</span><span class="p">[</span><span class="n">serialized_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="k">def</span> <span class="nf">from_dict</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;Model&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">        Deserialize dictionary to model instance.
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">        Handles field name mapping and type conversion.
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="n">kwargs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">serialized_name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="k">if</span> <span class="n">serialized_name</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">serialized_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="k">elif</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="c1"># Fallback to field name if serialized name not found</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">field_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Generate readable representation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">field_strs</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="n">field_strs</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">field_name</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">field_strs</span><span class="p">)</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Compare models by their field values.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">            <span class="k">if</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field_name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span></span></span></code></pre></div><h4 id="步驟-4加入巢狀-model-支援">步驟 4：加入巢狀 Model 支援</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">EmbeddedField</span><span class="p">(</span><span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    Embedded model field for nested structures.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Allows nesting Model instances within other Models.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">model_class</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">Model</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span> <span class="o">=</span> <span class="n">model_class</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="c1"># Accept dict and convert to model</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">elif</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2"> or dict, &#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Model</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Serialize embedded model to dict.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Model</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Deserialize dict to embedded model.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Django-style Field Descriptor - Complete Implementation
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">Demonstrates how to combine Descriptor protocol with Metaclass
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">to create a declarative API similar to Django Model Fields.
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="c1"># ===== Field Descriptors =====</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">Field</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base Field Descriptor with validation and serialization.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="n">default</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="n">required</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="n">serialized_name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="n">T</span><span class="p">],</span> <span class="nb">bool</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Validation failed&#34;</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="o">=</span> <span class="n">required</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="o">=</span> <span class="n">serialized_name</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="k">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_field_</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: This field is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">StringField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;String field with pattern and length validation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="n">pattern</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span> <span class="k">if</span> <span class="n">pattern</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected str, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum length is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum length is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Does not match required pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="k">class</span> <span class="nc">IntField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">int</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Integer field with range validation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected int, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum value is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum value is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="k">class</span> <span class="nc">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected bool, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="k">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Field with predefined choices.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">choices_str</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">repr</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Must be one of: </span><span class="si">{</span><span class="n">choices_str</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">class</span> <span class="nc">ListField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">list</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;List field with item type.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">item_field</span><span class="p">:</span> <span class="n">Field</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;default&#34;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;required&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">item_field</span> <span class="o">=</span> <span class="n">item_field</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected list, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span> <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="p">(</span><span class="n">value</span> <span class="ow">or</span> <span class="p">[])]</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="p">(</span><span class="n">value</span> <span class="ow">or</span> <span class="p">[])]</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="k">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with ISO format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">format</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">T%H:%M:%S&#34;</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">format</span> <span class="o">=</span> <span class="nb">format</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">datetime</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected datetime, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">datetime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">strptime</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># ===== Metaclass and Model =====</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="k">class</span> <span class="nc">ModelMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Metaclass that collects Field descriptors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">bases</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">namespace</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="n">fields</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Field</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="c1"># Inherit from parent classes</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s2">&#34;_fields&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                <span class="n">fields</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_fields</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="c1"># Collect from current class</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">                <span class="n">fields</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">attr_value</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">namespace</span><span class="p">[</span><span class="s2">&#34;_fields&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">fields</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">namespace</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl"><span class="k">class</span> <span class="nc">Model</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">ModelMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base Model with automatic serialization.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">    <span class="n">_fields</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Field</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">field</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl">        <span class="n">unknown</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="k">if</span> <span class="n">unknown</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown fields: </span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">unknown</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">
</span></span><span class="line"><span class="ln">213</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">            <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">required</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="n">key</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">                <span class="n">result</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">
</span></span><span class="line"><span class="ln">222</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="k">def</span> <span class="nf">from_dict</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;Model&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">        <span class="n">kwargs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">key</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">            <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">key</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">            <span class="k">elif</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">field_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">        <span class="n">parts</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">
</span></span><span class="line"><span class="ln">237</span><span class="cl">    <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">            <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="k">class</span> <span class="nc">EmbeddedField</span><span class="p">(</span><span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Embedded model field for nested structures.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">
</span></span><span class="line"><span class="ln">248</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">model_class</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">Model</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span> <span class="o">=</span> <span class="n">model_class</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">
</span></span><span class="line"><span class="ln">252</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">            <span class="k">elif</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2"> or dict&#34;</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Model</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Model</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ===== Define Models =====</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">HookSpecificOutput</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Nested model for hook-specific output.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">hook_event_name</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">choices</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;hookEventName&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">permission_decision</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">choices</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span> <span class="s2">&#34;ask&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;permissionDecision&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">permission_reason</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;permissionDecisionReason&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">user_prompt</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;userPrompt&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">class</span> <span class="nc">HookOutput</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    Hook Output model - similar to create_pretooluse_output
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    Declarative definition replaces the factory function.
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">hook_specific_output</span> <span class="o">=</span> <span class="n">EmbeddedField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">model_class</span><span class="o">=</span><span class="n">HookSpecificOutput</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;hookSpecificOutput&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">system_message</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;systemMessage&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">suppress_output</span> <span class="o">=</span> <span class="n">BoolField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;suppressOutput&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># ===== Usage Examples =====</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="c1"># Create model instance</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">hook_specific_output</span><span class="o">=</span><span class="n">HookSpecificOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="n">hook_event_name</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">permission_decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">permission_reason</span><span class="o">=</span><span class="s2">&#34;Operation permitted&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="n">system_message</span><span class="o">=</span><span class="s2">&#34;Check completed&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Created: </span><span class="si">{</span><span class="n">output</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="c1"># Serialize to dict (ready for JSON)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">output</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Serialized: </span><span class="si">{</span><span class="n">data</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="c1"># {</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="c1">#     &#34;hookSpecificOutput&#34;: {</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="c1">#         &#34;hookEventName&#34;: &#34;PreToolUse&#34;,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="c1">#         &#34;permissionDecision&#34;: &#34;allow&#34;,</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="c1">#         &#34;permissionDecisionReason&#34;: &#34;Operation permitted&#34;</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="c1">#     },</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">    <span class="c1">#     &#34;systemMessage&#34;: &#34;Check completed&#34;,</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="c1">#     &#34;suppressOutput&#34;: False</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="c1"># }</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl">    <span class="c1"># Deserialize from dict</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">restored</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Restored: </span><span class="si">{</span><span class="n">restored</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Equal: </span><span class="si">{</span><span class="n">output</span> <span class="o">==</span> <span class="n">restored</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="c1"># Validation examples</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="n">bad</span> <span class="o">=</span> <span class="n">HookSpecificOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">            <span class="n">hook_event_name</span><span class="o">=</span><span class="s2">&#34;InvalidEvent&#34;</span><span class="p">,</span>  <span class="c1"># Error: not in choices</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">            <span class="n">permission_decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">            <span class="n">permission_reason</span><span class="o">=</span><span class="s2">&#34;Test&#34;</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">86</span><span class="cl">
</span></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="c1"># Nested dict conversion</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">    <span class="n">output2</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">        <span class="n">hook_specific_output</span><span class="o">=</span><span class="p">{</span>  <span class="c1"># Auto-converts dict to model</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">            <span class="s2">&#34;hook_event_name&#34;</span><span class="p">:</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">            <span class="s2">&#34;permission_decision&#34;</span><span class="p">:</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl">            <span class="s2">&#34;permission_reason&#34;</span><span class="p">:</span> <span class="s2">&#34;Access denied&#34;</span>
</span></span><span class="line"><span class="ln">93</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">95</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;From nested dict: </span><span class="si">{</span><span class="n">output2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>執行結果</strong>：</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">Created: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=&#39;Check completed&#39;, suppress_output=False)
</span></span><span class="line"><span class="ln">2</span><span class="cl">Serialized: {&#39;hookSpecificOutput&#39;: {&#39;hookEventName&#39;: &#39;PreToolUse&#39;, &#39;permissionDecision&#39;: &#39;allow&#39;, &#39;permissionDecisionReason&#39;: &#39;Operation permitted&#39;}, &#39;systemMessage&#39;: &#39;Check completed&#39;, &#39;suppressOutput&#39;: False}
</span></span><span class="line"><span class="ln">3</span><span class="cl">Restored: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=&#39;Check completed&#39;, suppress_output=False)
</span></span><span class="line"><span class="ln">4</span><span class="cl">Equal: True
</span></span><span class="line"><span class="ln">5</span><span class="cl">Validation error: hook_event_name: Must be one of: &#39;PreToolUse&#39;, &#39;PostToolUse&#39;, &#39;Stop&#39;
</span></span><span class="line"><span class="ln">6</span><span class="cl">From nested dict: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=None, suppress_output=False)</span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>函式工廠模式</th>
          <th>Field Descriptor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>學習曲線</strong></td>
          <td>低，只需了解函式</td>
          <td>中，需理解 Descriptor + Metaclass</td>
      </tr>
      <tr>
          <td><strong>程式碼量</strong></td>
          <td>每個輸出類型一個函式</td>
          <td>一次性建立後可重用</td>
      </tr>
      <tr>
          <td><strong>欄位定義</strong></td>
          <td>分散在函式簽名和函式體</td>
          <td>集中在類別定義中</td>
      </tr>
      <tr>
          <td><strong>型別提示</strong></td>
          <td>需手動維護</td>
          <td>與欄位定義整合</td>
      </tr>
      <tr>
          <td><strong>序列化</strong></td>
          <td>每個函式獨立實作</td>
          <td>自動生成</td>
      </tr>
      <tr>
          <td><strong>驗證時機</strong></td>
          <td>呼叫時驗證</td>
          <td>賦值時驗證</td>
      </tr>
      <tr>
          <td><strong>巢狀結構</strong></td>
          <td>需手動處理</td>
          <td>EmbeddedField 自動處理</td>
      </tr>
      <tr>
          <td><strong>Schema 生成</strong></td>
          <td>無法自動化</td>
          <td>可從 _fields 元資料生成</td>
      </tr>
      <tr>
          <td><strong>調試</strong></td>
          <td>直覺，錯誤位置明確</td>
          <td>需理解 Descriptor 協議</td>
      </tr>
  </tbody>
</table>
<h3 id="關鍵差異說明">關鍵差異說明</h3>
<p><strong>函式工廠模式</strong>（如 hook_io.py）：</p>
<ul>
<li>適合簡單、一次性的資料建構</li>
<li>不需要額外的學習成本</li>
<li>但重複程式碼多，難以維護</li>
</ul>
<p><strong>Field Descriptor 模式</strong>：</p>
<ul>
<li>初期投入較高，但長期收益大</li>
<li>欄位定義即文檔</li>
<li>易於擴展和測試</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>ORM/ODM 設計（如 Django Model、MongoDB ODM）</li>
<li>配置檔案解析（型別安全的 YAML/JSON 解析）</li>
<li>API 請求/回應模型（REST API、GraphQL）</li>
<li>需要 Schema 自動生成的場景</li>
<li>多個類似結構的資料類別</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的資料容器（直接用 dataclass）</li>
<li>不需要序列化的內部類別</li>
<li>Pydantic 已經滿足需求</li>
<li>團隊不熟悉元編程</li>
<li>單一用途的資料結構</li>
</ul>
<h2 id="與-pydantic-的比較">與 Pydantic 的比較</h2>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>自製 Field Descriptor</th>
          <th>Pydantic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>型別驗證</td>
          <td>手動實作</td>
          <td>自動</td>
      </tr>
      <tr>
          <td>JSON Schema</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><strong>建議</strong>：在生產環境優先考慮 Pydantic；學習元編程時使用自製實作。</p>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="1-實作-boolfield-和-datetimefield">1. 實作 <code>BoolField</code> 和 <code>DateTimeField</code></h4>
<p>完成以下 Field 類別，支援型別驗證和序列化：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">class</span> <span class="nc">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field with strict type checking.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># TODO: implement</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with custom format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># TODO: implement</span></span></span></code></pre></div><h4 id="2-新增欄位的預設值和-required-屬性">2. 新增欄位的預設值和 required 屬性</h4>
<p>修改 Field 基類，讓以下程式碼能正確運作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">class</span> <span class="nc">Config</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">timeout</span> <span class="o">=</span> <span class="n">IntField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">debug</span> <span class="o">=</span> <span class="n">BoolField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 應該能建立實例</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">Config</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;test&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">assert</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">==</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="k">assert</span> <span class="n">config</span><span class="o">.</span><span class="n">debug</span> <span class="o">==</span> <span class="kc">False</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="3-實作巢狀-modelembeddedfield">3. 實作巢狀 Model（<code>EmbeddedField</code>）</h4>
<p>讓以下程式碼能正確運作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Address</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">city</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">street</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">Person</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">address</span> <span class="o">=</span> <span class="n">EmbeddedField</span><span class="p">(</span><span class="n">model_class</span><span class="o">=</span><span class="n">Address</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 從巢狀 dict 建立</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">person</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">from_dict</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Alice&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="s2">&#34;address&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="s2">&#34;Taipei&#34;</span><span class="p">,</span> <span class="s2">&#34;street&#34;</span><span class="p">:</span> <span class="s2">&#34;Main St&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 序列化回 dict</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">person</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span></span></span></code></pre></div><h4 id="4-實作-listfield-的完整驗證">4. 實作 ListField 的完整驗證</h4>
<p>讓以下程式碼能驗證列表中的每個項目：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Tag</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="n">pattern</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;^[a-z]+$&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">priority</span> <span class="o">=</span> <span class="n">IntField</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">Article</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">tags</span> <span class="o">=</span> <span class="n">ListField</span><span class="p">(</span><span class="n">item_field</span><span class="o">=</span><span class="n">EmbeddedField</span><span class="p">(</span><span class="n">model_class</span><span class="o">=</span><span class="n">Tag</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 每個 tag 都應該被驗證</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">article</span> <span class="o">=</span> <span class="n">Article</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Python Tips&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">tags</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;priority&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;INVALID&#34;</span><span class="p">,</span> <span class="s2">&#34;priority&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">}</span>  <span class="c1"># Should raise error</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="5-實作-schema-自動生成">5. 實作 Schema 自動生成</h4>
<p>為 Model 類別新增 <code>to_json_schema()</code> 類別方法，自動生成 JSON Schema：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">age</span> <span class="o">=</span> <span class="n">IntField</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="n">pattern</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;^[\w.-]+@[\w.-]+\.\w+$&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">schema</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">to_json_schema</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 應該輸出：</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># {</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">#     &#34;type&#34;: &#34;object&#34;,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">#     &#34;properties&#34;: {</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">#         &#34;name&#34;: {&#34;type&#34;: &#34;string&#34;, &#34;minLength&#34;: 1, &#34;maxLength&#34;: 100},</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">#         &#34;age&#34;: {&#34;type&#34;: &#34;integer&#34;, &#34;minimum&#34;: 0, &#34;maximum&#34;: 150},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">#         &#34;email&#34;: {&#34;type&#34;: &#34;string&#34;, &#34;pattern&#34;: &#34;^[\\w.-]+@[\\w.-]+\\.\\w+$&#34;}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">#     },</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">#     &#34;required&#34;: [&#34;name&#34;, &#34;age&#34;, &#34;email&#34;]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># }</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://github.com/django/django/blob/main/django/db/models/fields/__init__.py">Django Model Field 原始碼</a></li>
<li><a href="https://github.com/pydantic/pydantic">Pydantic Field 實作</a></li>
<li><a href="https://docs.python.org/3/howto/descriptor.html">Python Descriptor HOWTO</a></li>
<li><a href="https://www.attrs.org/">attrs 專案</a> - 另一個優秀的 Python 類別輔助工具</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a></em>
<em>返回：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></em></p>
]]></content:encoded></item><item><title>T.C4 Client-side log 缺失導致 debug 只能靠實機盲測</title><link>https://tarrragon.github.io/blog/testing/cases/client-log-absent-debug-cost/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/client-log-absent-debug-cost/</guid><description>&lt;p>這個案例的核心責任是說明「客戶端 log 設計」為什麼應該在功能企劃階段完成，而不是 debug 時才補。Log 不是 debug 工具，是可觀測性基礎設施。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 的六個核心元件在實機測試前的 log 覆蓋狀態：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>元件&lt;/th>
 &lt;th>log 點數&lt;/th>
 &lt;th>備註&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>ConnectionManager&lt;/td>
 &lt;td>0 → 10&lt;/td>
 &lt;td>W2 修復後補的 &lt;code>developer.log&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TerminalScreen&lt;/td>
 &lt;td>0 → 5&lt;/td>
 &lt;td>W2 修復後補的&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TtydProtocol&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>encode/decode/buildAuth 無 log&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BiometricService&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>isAvailable/authenticate 結果無 log&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CredentialRepository&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>load/save/delete 操作無 log&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>EnrollmentScreen&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>QR 掃描/解析/儲存無 log&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>W2-004（P0：iOS 實機 WS stream 不觸發）的 debug 過程：無法從任何 log 判斷問題發生在 biometric → credential → WS connect → auth token → stream listen 的哪一步。開發者被迫在每個函式手動加 &lt;code>developer.log&lt;/code>，重新編譯，插拔裝置測試，反覆數次才定位到「stream 訂閱時機」問題。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>debug 成本&lt;/td>
 &lt;td>每次修改→編譯→部署→測試約 3-5 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>定位 W2-002 (auth token) 花費&lt;/td>
 &lt;td>約 30 分鐘反覆測試&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>若有連線生命週期 log&lt;/td>
 &lt;td>第一次連線就能看到「Step 3 之後無 auth token 發送」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Log 缺失把 debug 成本從秒級升到分鐘級&lt;/strong>。如果 ConnectionManager 在企劃階段就設計了「Step 1: biometric → Step 2: credential → Step 3: WS connect → Step 4: auth token → Step 5: listen stream」五步 log，W2-002 的 auth token 問題在第一次連線就能從 log 看到「Step 3 完成，Step 4 未執行」。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>「事後補 log」的 log 品質較低&lt;/strong>。W2 修復時補的 &lt;code>developer.log&lt;/code> 格式不統一（有的帶 &lt;code>name:&lt;/code>，有的不帶；有的用 &lt;code>// i18n-exempt&lt;/code> 標記，有的忘了），沒有統一的 log 層級，沒有結構化欄位。事後補的 log 是救火工具，不是可觀測性設計。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>自用工具最適合自架 log 收集&lt;/strong>。app_tunnel 的 server 和 client 都在同一台機器上（或同一個 Tailscale tailnet），client 可以直接打 HTTP POST 到本機的 log endpoint，不需要 Sentry 或 Crashlytics。一個 Go 寫的 JSON log receiver（20 行）+ grep 就是完整的 debug 工具鏈。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Log 設計是功能規格的一部分&lt;/strong>。「連線到 ttyd 終端機」這個功能的規格不只是「建立 WS 連線」，還包含「每步有 log、失敗有 log、成功有 log」。跟 API 規格需要定義 request/response 一樣，連線功能需要定義 log 點。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>功能規格階段列出 log 點清單&lt;/strong>：每個功能的規格文件新增「可觀測性」欄位，列出啟動/步驟/錯誤/完成四類 log 點。&lt;/li>
&lt;li>&lt;strong>建立統一 log 層&lt;/strong>：封裝 &lt;code>developer.log&lt;/code> 為 &lt;code>AppLogger&lt;/code>，統一 name、level、格式。開發期用 &lt;code>developer.log&lt;/code>，後續可切換到 HTTP log endpoint。&lt;/li>
&lt;li>&lt;strong>自架 log endpoint 方案&lt;/strong>：本機 Go server 開一個 &lt;code>/log&lt;/code> POST endpoint，接收 JSON log，寫入檔案。Client 端 &lt;code>AppLogger&lt;/code> 在 debug mode 同時寫 console + POST 到 endpoint。開發期 grep 查詢，不需要 dashboard。&lt;/li>
&lt;li>&lt;strong>Protocol log 獨立一層&lt;/strong>：WebSocket frame type、payload 前綴、auth handshake 結果獨立記錄，跟 business log 分開。這層 log 在 release mode 應該能關閉。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計客戶端 log 方案 → &lt;a href="https://tarrragon.github.io/blog/testing/02-client-observability/" data-link-title="模組二：客戶端可觀測性" data-link-desc="連線生命週期 log、protocol 訊息 log、使用者行為 log — log 設計是功能規格的一部分">模組二：客戶端可觀測性&lt;/a>&lt;/li>
&lt;li>想理解三層 log 設計 → &lt;a href="https://tarrragon.github.io/blog/testing/02-client-observability/three-layer-log-design/" data-link-title="三層 log 設計" data-link-desc="連線生命週期 log、protocol 訊息 log、使用者行為 log — 三層各自的職責、詳細程度和啟停控制">三層 log 設計&lt;/a>&lt;/li>
&lt;li>想建自架 log endpoint → &lt;a href="https://tarrragon.github.io/blog/testing/02-client-observability/log-endpoint-tradeoff/" data-link-title="自架 log endpoint vs 商業方案的取捨判斷" data-link-desc="自用工具用自架 log receiver（20 行 Go &amp;#43; grep）、商業 app 用 Sentry/Crashlytics — 判斷依據是使用者規模和 debug 需求">自架 log endpoint vs 商業方案&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「客戶端 log 設計」為什麼應該在功能企劃階段完成，而不是 debug 時才補。Log 不是 debug 工具，是可觀測性基礎設施。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 的六個核心元件在實機測試前的 log 覆蓋狀態：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>log 點數</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ConnectionManager</td>
          <td>0 → 10</td>
          <td>W2 修復後補的 <code>developer.log</code></td>
      </tr>
      <tr>
          <td>TerminalScreen</td>
          <td>0 → 5</td>
          <td>W2 修復後補的</td>
      </tr>
      <tr>
          <td>TtydProtocol</td>
          <td>0</td>
          <td>encode/decode/buildAuth 無 log</td>
      </tr>
      <tr>
          <td>BiometricService</td>
          <td>0</td>
          <td>isAvailable/authenticate 結果無 log</td>
      </tr>
      <tr>
          <td>CredentialRepository</td>
          <td>0</td>
          <td>load/save/delete 操作無 log</td>
      </tr>
      <tr>
          <td>EnrollmentScreen</td>
          <td>0</td>
          <td>QR 掃描/解析/儲存無 log</td>
      </tr>
  </tbody>
</table>
<p>W2-004（P0：iOS 實機 WS stream 不觸發）的 debug 過程：無法從任何 log 判斷問題發生在 biometric → credential → WS connect → auth token → stream listen 的哪一步。開發者被迫在每個函式手動加 <code>developer.log</code>，重新編譯，插拔裝置測試，反覆數次才定位到「stream 訂閱時機」問題。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>debug 成本</td>
          <td>每次修改→編譯→部署→測試約 3-5 分鐘</td>
      </tr>
      <tr>
          <td>定位 W2-002 (auth token) 花費</td>
          <td>約 30 分鐘反覆測試</td>
      </tr>
      <tr>
          <td>若有連線生命週期 log</td>
          <td>第一次連線就能看到「Step 3 之後無 auth token 發送」</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>Log 缺失把 debug 成本從秒級升到分鐘級</strong>。如果 ConnectionManager 在企劃階段就設計了「Step 1: biometric → Step 2: credential → Step 3: WS connect → Step 4: auth token → Step 5: listen stream」五步 log，W2-002 的 auth token 問題在第一次連線就能從 log 看到「Step 3 完成，Step 4 未執行」。</p>
</li>
<li>
<p><strong>「事後補 log」的 log 品質較低</strong>。W2 修復時補的 <code>developer.log</code> 格式不統一（有的帶 <code>name:</code>，有的不帶；有的用 <code>// i18n-exempt</code> 標記，有的忘了），沒有統一的 log 層級，沒有結構化欄位。事後補的 log 是救火工具，不是可觀測性設計。</p>
</li>
<li>
<p><strong>自用工具最適合自架 log 收集</strong>。app_tunnel 的 server 和 client 都在同一台機器上（或同一個 Tailscale tailnet），client 可以直接打 HTTP POST 到本機的 log endpoint，不需要 Sentry 或 Crashlytics。一個 Go 寫的 JSON log receiver（20 行）+ grep 就是完整的 debug 工具鏈。</p>
</li>
<li>
<p><strong>Log 設計是功能規格的一部分</strong>。「連線到 ttyd 終端機」這個功能的規格不只是「建立 WS 連線」，還包含「每步有 log、失敗有 log、成功有 log」。跟 API 規格需要定義 request/response 一樣，連線功能需要定義 log 點。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>功能規格階段列出 log 點清單</strong>：每個功能的規格文件新增「可觀測性」欄位，列出啟動/步驟/錯誤/完成四類 log 點。</li>
<li><strong>建立統一 log 層</strong>：封裝 <code>developer.log</code> 為 <code>AppLogger</code>，統一 name、level、格式。開發期用 <code>developer.log</code>，後續可切換到 HTTP log endpoint。</li>
<li><strong>自架 log endpoint 方案</strong>：本機 Go server 開一個 <code>/log</code> POST endpoint，接收 JSON log，寫入檔案。Client 端 <code>AppLogger</code> 在 debug mode 同時寫 console + POST 到 endpoint。開發期 grep 查詢，不需要 dashboard。</li>
<li><strong>Protocol log 獨立一層</strong>：WebSocket frame type、payload 前綴、auth handshake 結果獨立記錄，跟 business log 分開。這層 log 在 release mode 應該能關閉。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計客戶端 log 方案 → <a href="/blog/testing/02-client-observability/" data-link-title="模組二：客戶端可觀測性" data-link-desc="連線生命週期 log、protocol 訊息 log、使用者行為 log — log 設計是功能規格的一部分">模組二：客戶端可觀測性</a></li>
<li>想理解三層 log 設計 → <a href="/blog/testing/02-client-observability/three-layer-log-design/" data-link-title="三層 log 設計" data-link-desc="連線生命週期 log、protocol 訊息 log、使用者行為 log — 三層各自的職責、詳細程度和啟停控制">三層 log 設計</a></li>
<li>想建自架 log endpoint → <a href="/blog/testing/02-client-observability/log-endpoint-tradeoff/" data-link-title="自架 log endpoint vs 商業方案的取捨判斷" data-link-desc="自用工具用自架 log receiver（20 行 Go &#43; grep）、商業 app 用 Sentry/Crashlytics — 判斷依據是使用者規模和 debug 需求">自架 log endpoint vs 商業方案</a></li>
</ul>
]]></content:encoded></item><item><title>U.C4 首頁缺配對入口按鈕、導航流未完整列出</title><link>https://tarrragon.github.io/blog/ux-design/cases/missing-enrollment-entry-point/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/missing-enrollment-entry-point/</guid><description>&lt;p>這個案例的核心責任是說明導航流設計必須覆蓋所有操作情境的入口，不只是最常用的那個。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>app_tunnel 首頁在 W2-001 修復前只有一個按鈕：Connect Terminal（對應 UC-02 日常連線）。配對功能（UC-01 首次配對）沒有入口 — &lt;code>EnrollmentScreen&lt;/code> 和 &lt;code>QrScannerScreen&lt;/code> 都存在且可運作，但首頁沒有按鈕導航過去。&lt;/p>
&lt;p>Router 定義了三條路由，全部可存取：&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="n">GoRouter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">routes:&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">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">HomeScreen&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">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/enrollment&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">EnrollmentScreen&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">GoRoute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nl">path:&lt;/span> &lt;span class="s1">&amp;#39;/terminal&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nl">builder:&lt;/span> &lt;span class="p">...&lt;/span> &lt;span class="n">TerminalScreen&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;/code>&lt;/pre>&lt;/div>&lt;p>但 HomeScreen 只有一個 &lt;code>context.go('/terminal')&lt;/code> 按鈕，&lt;code>/enrollment&lt;/code> 路由存在但從 UI 無法到達。&lt;/p>
&lt;p>W2-001 修復加入 &lt;code>OutlinedButton.icon&lt;/code> 連結到 &lt;code>/enrollment&lt;/code>，並用 &lt;code>context.push&lt;/code>（非 &lt;code>context.go&lt;/code>）讓配對完成後能返回首頁。&lt;/p>
&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;code>OutlinedButton&lt;/code> + &lt;code>context.push('/enrollment')&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>根因&lt;/td>
 &lt;td>導航流只設計了「日常連線」入口，遺漏「首次配對」入口&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>操作盤點有四個操作，首頁只有一個入口&lt;/strong>。操作盤點段列出四個操作：配對、連線、輪替、啟停。首頁應該是這四個操作的導航 hub，至少要有「配對」和「連線」兩個入口（輪替和啟停是主機端操作，不需要 app 入口）。只放 Connect Terminal 等於假設「使用者已經配對過」。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>路由存在但 UI 不可達 = 死程式碼的 UX 版本&lt;/strong>。&lt;code>/enrollment&lt;/code> 路由在 router 裡定義了，&lt;code>EnrollmentScreen&lt;/code> 也完整實作了，但使用者從 UI 無法觸及。這跟寫了函式但沒有呼叫者一樣 — 功能正確但不可存取。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;code>go&lt;/code> vs &lt;code>push&lt;/code> 的語意差異影響 UX&lt;/strong>。W2 修復用 &lt;code>context.push('/enrollment')&lt;/code> 而非 &lt;code>context.go('/enrollment')&lt;/code> — &lt;code>push&lt;/code> 保留返回堆疊讓使用者配對後按 back 回首頁；&lt;code>go&lt;/code> 替換整個路由堆疊、沒有 back。這個決策影響使用者的導航體驗，但也是事後才想到的。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>導航流從操作盤點反推&lt;/strong>：每個 UC（用例）的主入口在哪？首頁應該是哪些 UC 的 hub？列出來，確認每個 UC 至少有一條從首頁可達的路徑。&lt;/li>
&lt;li>&lt;strong>路由可達性檢查&lt;/strong>：router 定義的每個路由都應該從 UI 可達。不可達的路由要嘛是遺漏入口（本案例），要嘛是應該刪除的死路由。可以寫一個 lint 檢查。&lt;/li>
&lt;li>&lt;strong>首次 vs 日常使用者的 UX 區分&lt;/strong>：首次使用者需要 onboarding 流程（配對 → 連線），日常使用者只需要連線。兩種入口都要在首頁可見，但可以用視覺層級區分主要/次要。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計完整導航流 → &lt;a href="https://tarrragon.github.io/blog/ux-design/05-navigation-patterns/" data-link-title="模組五：導航模式" data-link-desc="Push/pop stack、GoRouter 命名路由、tab bar、drawer — 導航方法選擇是設計決策">模組五：導航模式&lt;/a>&lt;/li>
&lt;li>想檢查畫面狀態矩陣的退出路徑 → &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出&lt;/a>&lt;/li>
&lt;li>想做路由可達性自動化檢查 → 待補：Flutter GoRouter 路由可達性 lint&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明導航流設計必須覆蓋所有操作情境的入口，不只是最常用的那個。</p>
<h2 id="觀察">觀察</h2>
<p>app_tunnel 首頁在 W2-001 修復前只有一個按鈕：Connect Terminal（對應 UC-02 日常連線）。配對功能（UC-01 首次配對）沒有入口 — <code>EnrollmentScreen</code> 和 <code>QrScannerScreen</code> 都存在且可運作，但首頁沒有按鈕導航過去。</p>
<p>Router 定義了三條路由，全部可存取：</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="n">GoRouter</span><span class="p">(</span><span class="nl">routes:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">HomeScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/enrollment&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">EnrollmentScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="n">GoRoute</span><span class="p">(</span><span class="nl">path:</span> <span class="s1">&#39;/terminal&#39;</span><span class="p">,</span> <span class="nl">builder:</span> <span class="p">...</span> <span class="n">TerminalScreen</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">]);</span></span></span></code></pre></div><p>但 HomeScreen 只有一個 <code>context.go('/terminal')</code> 按鈕，<code>/enrollment</code> 路由存在但從 UI 無法到達。</p>
<p>W2-001 修復加入 <code>OutlinedButton.icon</code> 連結到 <code>/enrollment</code>，並用 <code>context.push</code>（非 <code>context.go</code>）讓配對完成後能返回首頁。</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>影響</td>
          <td>首次使用者無法配對（功能存在但入口缺失）</td>
      </tr>
      <tr>
          <td>修復</td>
          <td>加一個 <code>OutlinedButton</code> + <code>context.push('/enrollment')</code></td>
      </tr>
      <tr>
          <td>根因</td>
          <td>導航流只設計了「日常連線」入口，遺漏「首次配對」入口</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>操作盤點有四個操作，首頁只有一個入口</strong>。操作盤點段列出四個操作：配對、連線、輪替、啟停。首頁應該是這四個操作的導航 hub，至少要有「配對」和「連線」兩個入口（輪替和啟停是主機端操作，不需要 app 入口）。只放 Connect Terminal 等於假設「使用者已經配對過」。</p>
</li>
<li>
<p><strong>路由存在但 UI 不可達 = 死程式碼的 UX 版本</strong>。<code>/enrollment</code> 路由在 router 裡定義了，<code>EnrollmentScreen</code> 也完整實作了，但使用者從 UI 無法觸及。這跟寫了函式但沒有呼叫者一樣 — 功能正確但不可存取。</p>
</li>
<li>
<p><strong><code>go</code> vs <code>push</code> 的語意差異影響 UX</strong>。W2 修復用 <code>context.push('/enrollment')</code> 而非 <code>context.go('/enrollment')</code> — <code>push</code> 保留返回堆疊讓使用者配對後按 back 回首頁；<code>go</code> 替換整個路由堆疊、沒有 back。這個決策影響使用者的導航體驗，但也是事後才想到的。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li><strong>導航流從操作盤點反推</strong>：每個 UC（用例）的主入口在哪？首頁應該是哪些 UC 的 hub？列出來，確認每個 UC 至少有一條從首頁可達的路徑。</li>
<li><strong>路由可達性檢查</strong>：router 定義的每個路由都應該從 UI 可達。不可達的路由要嘛是遺漏入口（本案例），要嘛是應該刪除的死路由。可以寫一個 lint 檢查。</li>
<li><strong>首次 vs 日常使用者的 UX 區分</strong>：首次使用者需要 onboarding 流程（配對 → 連線），日常使用者只需要連線。兩種入口都要在首頁可見，但可以用視覺層級區分主要/次要。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計完整導航流 → <a href="/blog/ux-design/05-navigation-patterns/" data-link-title="模組五：導航模式" data-link-desc="Push/pop stack、GoRouter 命名路由、tab bar、drawer — 導航方法選擇是設計決策">模組五：導航模式</a></li>
<li>想檢查畫面狀態矩陣的退出路徑 → <a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出</a></li>
<li>想做路由可達性自動化檢查 → 待補：Flutter GoRouter 路由可達性 lint</li>
</ul>
]]></content:encoded></item><item><title>9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/</guid><description>&lt;p>這個案例的核心責任是說明「transactional 金融系統」如何在不可預期峰值下維持低延遲。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech&lt;/a> 對比 — GR8 Tech 走「微服務 + AI 預測擴容」、DraftKings 走「Aurora 單一資料庫服務支撐多 DB cluster」、兩條路徑都解決同類業務問題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>DraftKings 帳本系統的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings case study&lt;/a>）：&lt;/p>
&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>310 萬 unique customers / month (Q2 2024)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值操作&lt;/td>
 &lt;td>100 萬 ops / 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讀延遲&lt;/td>
 &lt;td>&amp;lt; 1 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫延遲&lt;/td>
 &lt;td>6 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Replication lag&lt;/td>
 &lt;td>從 30 秒降到 10-30 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database 數量&lt;/td>
 &lt;td>200 個 individual databases&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Super Bowl 流量&lt;/td>
 &lt;td>比賽季開幕高 +50%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon Aurora MySQL-Compatible、Aurora Replicas（讀寫分流）、Aurora I/O-Optimized（2023-05 推出）、Aurora Database Cloning（測試環境）、跨三個 AZ 儲存複製。&lt;/p>
&lt;p>關鍵負載形狀：「write workloads spike up significantly around payout events, but opening the app during the game also activates a lot of balance queries」— 比賽進行時是讀爆量、payout event 時是寫爆量、雙峰錯位。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>DraftKings 的工程選擇揭露三個 OLTP 容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>200 個獨立資料庫 = sharding 預先做好&lt;/strong>：按業務切 200 個 cluster、用巨型 cluster 撐全部在這個規模行不通。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 把「單機極限」改成「shard 極限」、每個 shard 的容量規劃變成獨立問題。&lt;/li>
&lt;li>&lt;strong>Replication lag 30 秒 → 10-30 ms&lt;/strong>：這個改善不只是「快」、而是讓 read-after-write 變得可預測。Aurora 的 storage layer 多 AZ 複製是這個 lag 改善的主因。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 replication lag 影響 transaction boundary 設計。&lt;/li>
&lt;li>&lt;strong>Super Bowl +50% 「no sweat」&lt;/strong>：這句話的工程意義是 &lt;em>提前做好容量規劃&lt;/em>、不是「Aurora 神奇」。寫 workload 預期可能 + 50%、整個 system headroom 預留至少 50%、加上 read replica 動態加減、才能讓 50% 增幅變成「不流汗」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的 headroom budget 與 event-driven scheduled scaling。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：100 萬 ops / 分鐘 = ~17K ops / 秒、跨 200 個 databases 平均下來每個 DB 約 80 ops / 秒。這不是「單一 DB 撐 100 萬 ops」、而是「200 shard 加總 100 萬」。讀案例時要看「峰值是分散到多少 shard」、不只看總數。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「transactional 金融系統」如何在不可預期峰值下維持低延遲。跟 <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> 對比 — GR8 Tech 走「微服務 + AI 預測擴容」、DraftKings 走「Aurora 單一資料庫服務支撐多 DB cluster」、兩條路徑都解決同類業務問題。</p>
<h2 id="觀察">觀察</h2>
<p>DraftKings 帳本系統的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>310 萬 unique customers / month (Q2 2024)</td>
      </tr>
      <tr>
          <td>峰值操作</td>
          <td>100 萬 ops / 分鐘</td>
      </tr>
      <tr>
          <td>讀延遲</td>
          <td>&lt; 1 ms</td>
      </tr>
      <tr>
          <td>寫延遲</td>
          <td>6 ms</td>
      </tr>
      <tr>
          <td>Replication lag</td>
          <td>從 30 秒降到 10-30 ms</td>
      </tr>
      <tr>
          <td>Database 數量</td>
          <td>200 個 individual databases</td>
      </tr>
      <tr>
          <td>Super Bowl 流量</td>
          <td>比賽季開幕高 +50%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon Aurora MySQL-Compatible、Aurora Replicas（讀寫分流）、Aurora I/O-Optimized（2023-05 推出）、Aurora Database Cloning（測試環境）、跨三個 AZ 儲存複製。</p>
<p>關鍵負載形狀：「write workloads spike up significantly around payout events, but opening the app during the game also activates a lot of balance queries」— 比賽進行時是讀爆量、payout event 時是寫爆量、雙峰錯位。</p>
<h2 id="判讀">判讀</h2>
<p>DraftKings 的工程選擇揭露三個 OLTP 容量設計重點。</p>
<ol>
<li><strong>200 個獨立資料庫 = sharding 預先做好</strong>：按業務切 200 個 cluster、用巨型 cluster 撐全部在這個規模行不通。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 把「單機極限」改成「shard 極限」、每個 shard 的容量規劃變成獨立問題。</li>
<li><strong>Replication lag 30 秒 → 10-30 ms</strong>：這個改善不只是「快」、而是讓 read-after-write 變得可預測。Aurora 的 storage layer 多 AZ 複製是這個 lag 改善的主因。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 replication lag 影響 transaction boundary 設計。</li>
<li><strong>Super Bowl +50% 「no sweat」</strong>：這句話的工程意義是 <em>提前做好容量規劃</em>、不是「Aurora 神奇」。寫 workload 預期可能 + 50%、整個 system headroom 預留至少 50%、加上 read replica 動態加減、才能讓 50% 增幅變成「不流汗」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 headroom budget 與 event-driven scheduled scaling。</li>
</ol>
<p>需要警惕：100 萬 ops / 分鐘 = ~17K ops / 秒、跨 200 個 databases 平均下來每個 DB 約 80 ops / 秒。這不是「單一 DB 撐 100 萬 ops」、而是「200 shard 加總 100 萬」。讀案例時要看「峰值是分散到多少 shard」、不只看總數。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>按業務切 OLTP cluster、不要一個 DB 撐全部</strong>：DraftKings 200 個 databases 顯示「業務切片」是 OLTP 擴容的前置。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design 與 partition 決策。</li>
<li><strong>讀寫分流是 OLTP 容量規劃的基線</strong>：6ms 寫 vs &lt;1ms 讀的差距、加上 read replica、是 OLTP 擴容最基本的兩個槓桿。</li>
<li><strong>事件型峰值預測寫進 baseline</strong>：Super Bowl 是已知事件、+50% 是歷史經驗、所以可以提前 pre-scale。事件未知（突發新聞、KOL 推廣）的情況才需要 AI 預測（對照 <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a>）。</li>
</ol>
<p>跨平台等效：GCP Cloud SQL + read replica / Spanner、Azure Database for PostgreSQL + read replica、自建 PostgreSQL + Patroni + pgbouncer 都可以實作對等架構。Aurora 的差異是 storage layer 對 replica 的 lag 改善。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 OLTP 高峰容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想搞清楚事件型 vs 突發型峰值 → <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> 對照</li>
<li>想做 read replica 容量設計 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想理解 replication lag 對 transaction boundary 的影響 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想理解 6 寫 / 4 讀 quorum 跟 200 cluster fleet 治理 → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a></li>
<li>想規劃 read replica scaling 與 reader endpoint 路由 → <a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">Aurora read replica scaling</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings Scales Its Financial Ledger with Amazon Aurora</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-i-o-optimized-database-storage-configuration/">Aurora I/O-Optimized announcement</a></li>
</ul>
]]></content:encoded></item><item><title>2.C4 Meta：CacheLib / Kangaroo 分層快取</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cachelib-kangaroo-tiered-cache/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cachelib-kangaroo-tiered-cache/</guid><description>&lt;p>這個案例的核心責任是說明快取容量壓力升高後，策略會從單層記憶體轉向分層管理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Meta 透過 CacheLib 與 Kangaroo 把快取結構擴展到記憶體與快閃分層，改善容量與成本平衡。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當熱門資料集合超過 DRAM 經濟範圍時，單層快取會同時遇到成本與命中率瓶頸。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>定義不同資料熱度的落層策略。&lt;/li>
&lt;li>把 eviction 與回補延遲納入共同指標。&lt;/li>
&lt;li>驗證分層後 tail latency 與成本曲線。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3 TTL/eviction&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity/cost&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2021/04/09/core-data/cachelib/">CacheLib and Kangaroo&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取容量壓力升高後，策略會從單層記憶體轉向分層管理。</p>
<h2 id="觀察">觀察</h2>
<p>Meta 透過 CacheLib 與 Kangaroo 把快取結構擴展到記憶體與快閃分層，改善容量與成本平衡。</p>
<h2 id="判讀">判讀</h2>
<p>當熱門資料集合超過 DRAM 經濟範圍時，單層快取會同時遇到成本與命中率瓶頸。</p>
<h2 id="策略">策略</h2>
<ol>
<li>定義不同資料熱度的落層策略。</li>
<li>把 eviction 與回補延遲納入共同指標。</li>
<li>驗證分層後 tail latency 與成本曲線。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3 TTL/eviction</a> 與 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity/cost</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2021/04/09/core-data/cachelib/">CacheLib and Kangaroo</a></li>
</ul>
]]></content:encoded></item><item><title>3.C4 LinkedIn：Kafka 分層叢集治理</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/</guid><description>&lt;p>LinkedIn 的 Kafka 分層叢集案例呈現了 Kafka 在規模化之後，瓶頸從「broker 容量」轉移到「workload 互相干擾」。分層的核心判斷是按業務風險隔離，把叢集當成資源治理單位。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>LinkedIn 是 Kafka 的誕生地，內部 Kafka 叢集承載的工作負載涵蓋即時推薦、搜尋索引更新、analytics pipeline、audit log 跟 monitoring。早期所有 workload 共用少數幾個大叢集，隨流量成長，叢集內不同 workload 的資源競爭開始互相影響。&lt;/p>
&lt;p>LinkedIn 的 Kafka 規模是全球最大的之一 — 數千個 broker、每秒數百萬筆訊息、PB 級資料保留。在這個規模下，單一叢集的容量限制是 broker 數量跟 ZooKeeper 的 metadata 管理上限，但更早觸及的限制是 workload 之間的干擾。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="noisy-neighbor">Noisy neighbor&lt;/h3>
&lt;p>即時推薦系統需要低延遲的 consumer（P99 &amp;lt; 50ms），analytics pipeline 是大量 batch consumer（高吞吐但延遲容忍到秒級）。兩者共用同一組 broker 時，batch consumer 的大範圍 sequential read 佔滿 disk I/O，擠壓即時推薦的 random read latency。&lt;/p>
&lt;p>一個 analytics job 的重跑（backfill 歷史資料）可以讓推薦系統的 consumer lag 從毫秒跳到秒級。在共享叢集中，這種干擾難以預防 — 只能在事後發現、人工協調。&lt;/p>
&lt;h3 id="broker-故障的影響面">Broker 故障的影響面&lt;/h3>
&lt;p>單一叢集中 broker 故障會觸發 partition reassignment，reassignment 的資料搬移佔用 disk I/O 跟網路頻寬。在混合 workload 的叢集中，reassignment 同時影響所有 workload 的效能 — 包括跟故障 broker 無直接關係的 topic。&lt;/p>
&lt;p>叢集越大、topic 越多、reassignment 的影響面越廣。&lt;/p>
&lt;h3 id="容量規劃的模糊邊界">容量規劃的模糊邊界&lt;/h3>
&lt;p>共享叢集的容量規劃沒有清楚的 owner — analytics 團隊說「我們需要更多 retention」、推薦團隊說「我們需要更低 latency」、audit 團隊說「我們的資料不能丟」。三種需求各自合理，但共享叢集無法同時最佳化。&lt;/p>
&lt;h2 id="解法分層叢集">解法：分層叢集&lt;/h2>
&lt;p>LinkedIn 按業務風險跟效能需求把 workload 分配到不同叢集：&lt;/p>
&lt;p>&lt;strong>Tier 1 — 即時關鍵路徑&lt;/strong>：即時推薦、搜尋索引更新、使用者通知。Broker 配置偏向低延遲（SSD、高 IOPS）、replication factor 3、retention 短（保留足夠的 consumer catchup 時間）。&lt;/p>
&lt;p>&lt;strong>Tier 2 — 可靠性要求高但延遲容忍&lt;/strong>：audit log、合規事件、支付事件。配置偏向持久性（replication factor 3、min.insync.replicas 2、acks=all）、retention 長。&lt;/p>
&lt;p>&lt;strong>Tier 3 — 高吞吐分析&lt;/strong>：analytics pipeline、ETL、batch processing。配置偏向吞吐（大 batch size、長 linger.ms、HDD）、retention 最長、容忍偶發 consumer lag。&lt;/p>
&lt;h3 id="分層的判準">分層的判準&lt;/h3>
&lt;p>分層的判準是「這個 workload 故障時，業務影響有多大、多快」：&lt;/p>
&lt;ul>
&lt;li>即時影響使用者體驗 → Tier 1&lt;/li>
&lt;li>影響合規或財務但可容忍分鐘級延遲 → Tier 2&lt;/li>
&lt;li>影響分析準確性但可容忍小時級延遲 → Tier 3&lt;/li>
&lt;/ul>
&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>高（所有 workload 共用資源池）&lt;/td>
 &lt;td>低到中（每層有獨立的保留容量）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>隔離性&lt;/td>
 &lt;td>低（noisy neighbor 互相干擾）&lt;/td>
 &lt;td>高（故障跟效能退化限制在同層）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>運維複雜度&lt;/td>
 &lt;td>低（一組 broker 統一管理）&lt;/td>
 &lt;td>高（多組 broker、各自的監控跟維護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量規劃清晰度&lt;/td>
 &lt;td>模糊（多種需求混合、難以歸因）&lt;/td>
 &lt;td>清楚（每層的需求跟 owner 明確）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>故障影響面&lt;/td>
 &lt;td>廣（reassignment 影響所有 topic）&lt;/td>
 &lt;td>有限（reassignment 只影響同層）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分層的成本是資源利用率下降 — 每層都需要保留一定的 headroom 應對高峰，加總起來比共享叢集多。LinkedIn 的判斷是隔離性的價值大於利用率的損失 — 推薦系統一次 P99 退化的業務損失遠大於多幾台 broker 的成本。&lt;/p></description><content:encoded><![CDATA[<p>LinkedIn 的 Kafka 分層叢集案例呈現了 Kafka 在規模化之後，瓶頸從「broker 容量」轉移到「workload 互相干擾」。分層的核心判斷是按業務風險隔離，把叢集當成資源治理單位。</p>
<h2 id="業務背景">業務背景</h2>
<p>LinkedIn 是 Kafka 的誕生地，內部 Kafka 叢集承載的工作負載涵蓋即時推薦、搜尋索引更新、analytics pipeline、audit log 跟 monitoring。早期所有 workload 共用少數幾個大叢集，隨流量成長，叢集內不同 workload 的資源競爭開始互相影響。</p>
<p>LinkedIn 的 Kafka 規模是全球最大的之一 — 數千個 broker、每秒數百萬筆訊息、PB 級資料保留。在這個規模下，單一叢集的容量限制是 broker 數量跟 ZooKeeper 的 metadata 管理上限，但更早觸及的限制是 workload 之間的干擾。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="noisy-neighbor">Noisy neighbor</h3>
<p>即時推薦系統需要低延遲的 consumer（P99 &lt; 50ms），analytics pipeline 是大量 batch consumer（高吞吐但延遲容忍到秒級）。兩者共用同一組 broker 時，batch consumer 的大範圍 sequential read 佔滿 disk I/O，擠壓即時推薦的 random read latency。</p>
<p>一個 analytics job 的重跑（backfill 歷史資料）可以讓推薦系統的 consumer lag 從毫秒跳到秒級。在共享叢集中，這種干擾難以預防 — 只能在事後發現、人工協調。</p>
<h3 id="broker-故障的影響面">Broker 故障的影響面</h3>
<p>單一叢集中 broker 故障會觸發 partition reassignment，reassignment 的資料搬移佔用 disk I/O 跟網路頻寬。在混合 workload 的叢集中，reassignment 同時影響所有 workload 的效能 — 包括跟故障 broker 無直接關係的 topic。</p>
<p>叢集越大、topic 越多、reassignment 的影響面越廣。</p>
<h3 id="容量規劃的模糊邊界">容量規劃的模糊邊界</h3>
<p>共享叢集的容量規劃沒有清楚的 owner — analytics 團隊說「我們需要更多 retention」、推薦團隊說「我們需要更低 latency」、audit 團隊說「我們的資料不能丟」。三種需求各自合理，但共享叢集無法同時最佳化。</p>
<h2 id="解法分層叢集">解法：分層叢集</h2>
<p>LinkedIn 按業務風險跟效能需求把 workload 分配到不同叢集：</p>
<p><strong>Tier 1 — 即時關鍵路徑</strong>：即時推薦、搜尋索引更新、使用者通知。Broker 配置偏向低延遲（SSD、高 IOPS）、replication factor 3、retention 短（保留足夠的 consumer catchup 時間）。</p>
<p><strong>Tier 2 — 可靠性要求高但延遲容忍</strong>：audit log、合規事件、支付事件。配置偏向持久性（replication factor 3、min.insync.replicas 2、acks=all）、retention 長。</p>
<p><strong>Tier 3 — 高吞吐分析</strong>：analytics pipeline、ETL、batch processing。配置偏向吞吐（大 batch size、長 linger.ms、HDD）、retention 最長、容忍偶發 consumer lag。</p>
<h3 id="分層的判準">分層的判準</h3>
<p>分層的判準是「這個 workload 故障時，業務影響有多大、多快」：</p>
<ul>
<li>即時影響使用者體驗 → Tier 1</li>
<li>影響合規或財務但可容忍分鐘級延遲 → Tier 2</li>
<li>影響分析準確性但可容忍小時級延遲 → Tier 3</li>
</ul>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>共享叢集</th>
          <th>分層叢集</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資源利用率</td>
          <td>高（所有 workload 共用資源池）</td>
          <td>低到中（每層有獨立的保留容量）</td>
      </tr>
      <tr>
          <td>隔離性</td>
          <td>低（noisy neighbor 互相干擾）</td>
          <td>高（故障跟效能退化限制在同層）</td>
      </tr>
      <tr>
          <td>運維複雜度</td>
          <td>低（一組 broker 統一管理）</td>
          <td>高（多組 broker、各自的監控跟維護）</td>
      </tr>
      <tr>
          <td>容量規劃清晰度</td>
          <td>模糊（多種需求混合、難以歸因）</td>
          <td>清楚（每層的需求跟 owner 明確）</td>
      </tr>
      <tr>
          <td>故障影響面</td>
          <td>廣（reassignment 影響所有 topic）</td>
          <td>有限（reassignment 只影響同層）</td>
      </tr>
  </tbody>
</table>
<p>分層的成本是資源利用率下降 — 每層都需要保留一定的 headroom 應對高峰，加總起來比共享叢集多。LinkedIn 的判斷是隔離性的價值大於利用率的損失 — 推薦系統一次 P99 退化的業務損失遠大於多幾台 broker 的成本。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 配置怎麼影響延遲 vs 吞吐 vs 持久性的取捨。</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：不同 tier 的 Kafka 叢集各自有不同的 reliability budget。</li>
<li><a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a>：batch consumer 跟 real-time consumer 的資源消耗差異。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>即時消費者的 consumer lag 因為同叢集的 batch job 而上升</li>
<li>Broker 故障後的 partition reassignment 影響到跟故障無關的 topic</li>
<li>容量規劃會議中不同團隊的需求互相矛盾、無法在同一組配置中滿足</li>
<li>Kafka 叢集的 topic 數量超過 500 個、workload 類型超過三種</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/kafka/running-kafka-scale">Running Kafka at Scale at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>4.C4 AWS：X-Ray 到 OpenTelemetry 轉換</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/</guid><description>&lt;p>這個案例的核心責任是把觀測遷移從工具替換，提升為標準化策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>AWS 已明確提出 X-Ray SDK/Daemon 的維護時程，並提供遷移到 OpenTelemetry 的路徑。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 observability agent 與 SDK 受限於單一供應商，轉向 OTel 可以降低未來轉移成本，但需要治理採集、匯出與語意對齊。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先盤點現有 instrumentation 與依賴 SDK。&lt;/li>
&lt;li>先換 collector/agent，再逐步改應用端 instrumentation。&lt;/li>
&lt;li>把 trace/metric 的等價驗證納入 release gate。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 telemetry data quality&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-migration.html">X-Ray to OpenTelemetry migration guide&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把觀測遷移從工具替換，提升為標準化策略。</p>
<h2 id="觀察">觀察</h2>
<p>AWS 已明確提出 X-Ray SDK/Daemon 的維護時程，並提供遷移到 OpenTelemetry 的路徑。</p>
<h2 id="判讀">判讀</h2>
<p>當 observability agent 與 SDK 受限於單一供應商，轉向 OTel 可以降低未來轉移成本，但需要治理採集、匯出與語意對齊。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先盤點現有 instrumentation 與依賴 SDK。</li>
<li>先換 collector/agent，再逐步改應用端 instrumentation。</li>
<li>把 trace/metric 的等價驗證納入 release gate。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 telemetry data quality</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-migration.html">X-Ray to OpenTelemetry migration guide</a></li>
</ul>
]]></content:encoded></item><item><title>5.C4 Mobileye：Workloads 遷移到 EKS</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/mobileye-workloads-to-eks/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/mobileye-workloads-to-eks/</guid><description>&lt;p>這個案例的核心責任是把 workload 遷移從基礎設施作業改成服務可用性作業。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Mobileye 將大規模工作負載遷移到 EKS。遷移動機集中在運維一致性與可用性治理——原有環境中不同團隊各自維護部署流程，升級節奏、監控覆蓋、容量規劃的標準不統一。遷移目標是用 managed 平台統一這些操作基線，讓各團隊可以專注在 workload 本身。&lt;/p>
&lt;p>遷移範圍涵蓋多種 workload 類型：API 服務、資料處理 pipeline、ML 推論服務。這些 workload 的啟動時間、資源需求、drain 條件差異顯著，同一套遷移策略無法直接套用。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>工作負載遷移若缺乏分段驗證，容易在切流時放大依賴與資源風險。這個判讀的具體含義是：workload 從舊平台搬到新平台時，表面上看 pod 跑起來了、health check 通過了，但依賴路徑（資料庫連線、cache endpoint、queue consumer 註冊）可能還指向舊環境。這類錯位在小流量時不明顯，放大流量後才暴露延遲升高或認證失敗。&lt;/p>
&lt;p>另一個判讀是容量假設需要重新驗證。舊平台的 resource request/limit、HPA 設定是在舊環境的 node type、網路拓樸下校準的。新平台的 node 規格、storage driver、CNI 可能不同，原本的容量假設可能過鬆或過緊。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>分批遷移 workload、保留觀測對照&lt;/strong>：先遷移影響面小、依賴單純的 workload（如內部工具、非關鍵 API）。新舊平台同時跑相同 workload 時，比較 error rate、latency、資源使用率。觀測對照是驗證的基礎——沒有對照就無法判斷新平台行為是否符合預期。&lt;/li>
&lt;li>&lt;strong>明確定義每批次切換與回退條件&lt;/strong>：每批遷移前寫下「什麼條件算成功」和「什麼條件觸發回退」。成功條件用 SLI 偏差衡量（error rate 不超過基線 + N%、p99 latency 不超過基線 + M ms）。回退條件要可操作——回退腳本事先驗證、DNS/LB 規則切回路徑事先測試。&lt;/li>
&lt;li>&lt;strong>新平台先驗證容量與恢復節奏&lt;/strong>：在新平台上跑容量測試，確認 HPA 觸發、node scale-up、pod scheduling 的時間符合預期。恢復節奏驗證包含模擬 node 失效後 pod 重新調度的時間、模擬 deployment rollback 的完成時間。&lt;/li>
&lt;li>&lt;strong>workload 類型分群遷移&lt;/strong>：API 服務、batch job、ML 推論的遷移順序與驗證條件不同。API 服務看延遲與錯誤率；batch job 看完成時間與資料正確性；ML 推論看推論延遲與 GPU 資源分配。混在一批遷移會讓驗證條件模糊。&lt;/li>
&lt;/ol>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>這類遷移的回退判讀重點是「回退到舊平台時，舊平台是否仍在可服務狀態」。遷移進行中若舊平台的資源已被縮減（node 數降低、monitoring 設定已移除），回退路徑就失效。穩定做法是在該批 workload 的新平台觀測窗口結束前，舊平台維持原規模不動。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment&lt;/a> 看分階段平台遷移的流量切換節奏。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 platform lifecycle contract&lt;/a> 看不同 workload 類型的 lifecycle 差異。回 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review&lt;/a> 看遷移前的可靠性評估。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/mobileye-amazon-eks/">Mobileye migration to Amazon EKS&lt;/a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 workload 遷移從基礎設施作業改成服務可用性作業。</p>
<h2 id="觀察">觀察</h2>
<p>Mobileye 將大規模工作負載遷移到 EKS。遷移動機集中在運維一致性與可用性治理——原有環境中不同團隊各自維護部署流程，升級節奏、監控覆蓋、容量規劃的標準不統一。遷移目標是用 managed 平台統一這些操作基線，讓各團隊可以專注在 workload 本身。</p>
<p>遷移範圍涵蓋多種 workload 類型：API 服務、資料處理 pipeline、ML 推論服務。這些 workload 的啟動時間、資源需求、drain 條件差異顯著，同一套遷移策略無法直接套用。</p>
<h2 id="判讀">判讀</h2>
<p>工作負載遷移若缺乏分段驗證，容易在切流時放大依賴與資源風險。這個判讀的具體含義是：workload 從舊平台搬到新平台時，表面上看 pod 跑起來了、health check 通過了，但依賴路徑（資料庫連線、cache endpoint、queue consumer 註冊）可能還指向舊環境。這類錯位在小流量時不明顯，放大流量後才暴露延遲升高或認證失敗。</p>
<p>另一個判讀是容量假設需要重新驗證。舊平台的 resource request/limit、HPA 設定是在舊環境的 node type、網路拓樸下校準的。新平台的 node 規格、storage driver、CNI 可能不同，原本的容量假設可能過鬆或過緊。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>分批遷移 workload、保留觀測對照</strong>：先遷移影響面小、依賴單純的 workload（如內部工具、非關鍵 API）。新舊平台同時跑相同 workload 時，比較 error rate、latency、資源使用率。觀測對照是驗證的基礎——沒有對照就無法判斷新平台行為是否符合預期。</li>
<li><strong>明確定義每批次切換與回退條件</strong>：每批遷移前寫下「什麼條件算成功」和「什麼條件觸發回退」。成功條件用 SLI 偏差衡量（error rate 不超過基線 + N%、p99 latency 不超過基線 + M ms）。回退條件要可操作——回退腳本事先驗證、DNS/LB 規則切回路徑事先測試。</li>
<li><strong>新平台先驗證容量與恢復節奏</strong>：在新平台上跑容量測試，確認 HPA 觸發、node scale-up、pod scheduling 的時間符合預期。恢復節奏驗證包含模擬 node 失效後 pod 重新調度的時間、模擬 deployment rollback 的完成時間。</li>
<li><strong>workload 類型分群遷移</strong>：API 服務、batch job、ML 推論的遷移順序與驗證條件不同。API 服務看延遲與錯誤率；batch job 看完成時間與資料正確性；ML 推論看推論延遲與 GPU 資源分配。混在一批遷移會讓驗證條件模糊。</li>
</ol>
<h2 id="回退判讀">回退判讀</h2>
<p>這類遷移的回退判讀重點是「回退到舊平台時，舊平台是否仍在可服務狀態」。遷移進行中若舊平台的資源已被縮減（node 數降低、monitoring 設定已移除），回退路徑就失效。穩定做法是在該批 workload 的新平台觀測窗口結束前，舊平台維持原規模不動。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment</a> 看分階段平台遷移的流量切換節奏。回 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 platform lifecycle contract</a> 看不同 workload 類型的 lifecycle 差異。回 <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a> 看遷移前的可靠性評估。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/mobileye-amazon-eks/">Mobileye migration to Amazon EKS</a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）</li>
</ul>
]]></content:encoded></item><item><title>7.C4 Microsoft：Storm-0558 簽章金鑰事件</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/microsoft-storm-0558-signing-key-2023/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/microsoft-storm-0558-signing-key-2023/</guid><description>&lt;p>這個案例的核心責任是把身份簽章事件轉成長期信任治理問題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Storm-0558 事件揭露簽章金鑰與驗證流程一旦失守，會跨租戶影響身份驗證信任。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>此類事件的重點不只在修補漏洞，而在重建 key lifecycle、issuer 驗證與審計可見性。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>重新定義 key issuance 與 rotation 流程。&lt;/li>
&lt;li>強化 token 驗證路徑與異常檢測。&lt;/li>
&lt;li>讓身份證據鏈可被 incident 與稽核共用。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6 secrets/credentials&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 audit/accountability&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.microsoft.com/en-us/security/blog/2023/09/06/analysis-of-storm-0558-technique-and-microsofts-response/">Microsoft analysis of Storm-0558&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把身份簽章事件轉成長期信任治理問題。</p>
<h2 id="觀察">觀察</h2>
<p>Storm-0558 事件揭露簽章金鑰與驗證流程一旦失守，會跨租戶影響身份驗證信任。</p>
<h2 id="判讀">判讀</h2>
<p>此類事件的重點不只在修補漏洞，而在重建 key lifecycle、issuer 驗證與審計可見性。</p>
<h2 id="策略">策略</h2>
<ol>
<li>重新定義 key issuance 與 rotation 流程。</li>
<li>強化 token 驗證路徑與異常檢測。</li>
<li>讓身份證據鏈可被 incident 與稽核共用。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6 secrets/credentials</a> 與 <a href="/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 audit/accountability</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en-us/security/blog/2023/09/06/analysis-of-storm-0558-technique-and-microsofts-response/">Microsoft analysis of Storm-0558</a></li>
</ul>
]]></content:encoded></item><item><title>Cloudflare 2023 Workers KV Deployment Tool Misconfiguration</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-workers-kv-deployment-tool-misconfiguration/</guid><description>&lt;p>這起事件的核心責任判讀是：控制面工具設定錯誤會跨越產品邊界擴散，事故第一步要先切斷擴散路徑，再做功能修復。若先把症狀拆成多個產品問題，恢復速度會被 shared dependency 拖慢。&lt;/p>
&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cloudflare 在 2023-10-30 發生控制面相關事故，根因涉及 deployment tool 的設定錯誤，影響 Workers KV 與相關服務操作路徑。表面症狀可出現在多個產品面向，但本質是共享控制面變更帶來的連鎖失效。&lt;/p>
&lt;p>這類事故和單點 runtime bug 不同。關鍵不是「哪個服務先報錯」，而是「哪個共用控制點先失真」。&lt;/p>
&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>shared control dependency 可能失效&lt;/td>
 &lt;td>先盤點同批變更與共用工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>功能異常分布不均&lt;/td>
 &lt;td>擴散沿著控制面依賴鏈條走&lt;/td>
 &lt;td>用 dependency map 排定恢復優先順序&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>ownership 與指揮節奏不足&lt;/td>
 &lt;td>固定 single incident commander 與節點交接&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="事故路徑">事故路徑&lt;/h2>
&lt;ol>
&lt;li>控制面 deployment tool 變更進入生產。&lt;/li>
&lt;li>設定錯誤導致共享控制路徑失真。&lt;/li>
&lt;li>Workers KV 與關聯產品出現控制操作異常。&lt;/li>
&lt;li>團隊透過回退與修正逐步收斂錯誤。&lt;/li>
&lt;li>事故後回寫 deployment guardrail、decision log 與 evidence 管線。&lt;/li>
&lt;/ol>
&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>強制 staged rollout + canary gate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>決策紀錄&lt;/td>
 &lt;td>假設與回退條件在事中容易遺失&lt;/td>
 &lt;td>強制使用 [8.19] 決策欄位模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>證據回寫&lt;/td>
 &lt;td>教訓停留在事件敘事&lt;/td>
 &lt;td>連到 [8.22]，把證據回寫到 observability/reliability 控制面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則推送安全閘門&lt;/td>
 &lt;td>變更工具缺少風險分級&lt;/td>
 &lt;td>回寫 [6.24] 的 rule rollout gate&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>事故決策紀錄： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>&lt;/li>
&lt;li>事故證據回寫： &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>&lt;/li>
&lt;li>規則推送安全閘門： &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a>&lt;/li>
&lt;li>觀測治理模型： &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/cloudflare-incident-on-october-30-2023/">Cloudflare incident on October 30, 2023&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這起事件的核心責任判讀是：控制面工具設定錯誤會跨越產品邊界擴散，事故第一步要先切斷擴散路徑，再做功能修復。若先把症狀拆成多個產品問題，恢復速度會被 shared dependency 拖慢。</p>
<h2 id="事故摘要">事故摘要</h2>
<p>Cloudflare 在 2023-10-30 發生控制面相關事故，根因涉及 deployment tool 的設定錯誤，影響 Workers KV 與相關服務操作路徑。表面症狀可出現在多個產品面向，但本質是共享控制面變更帶來的連鎖失效。</p>
<p>這類事故和單點 runtime bug 不同。關鍵不是「哪個服務先報錯」，而是「哪個共用控制點先失真」。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>代表意義</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多產品控制操作同時不穩</td>
          <td>shared control dependency 可能失效</td>
          <td>先盤點同批變更與共用工具</td>
      </tr>
      <tr>
          <td>功能異常分布不均</td>
          <td>擴散沿著控制面依賴鏈條走</td>
          <td>用 dependency map 排定恢復優先順序</td>
      </tr>
      <tr>
          <td>回退後錯誤率快速下降</td>
          <td>變更關聯度高</td>
          <td>凍結同類變更、啟動增量復原</td>
      </tr>
      <tr>
          <td>事故中角色交接反覆切換</td>
          <td>ownership 與指揮節奏不足</td>
          <td>固定 single incident commander 與節點交接</td>
      </tr>
  </tbody>
</table>
<h2 id="事故路徑">事故路徑</h2>
<ol>
<li>控制面 deployment tool 變更進入生產。</li>
<li>設定錯誤導致共享控制路徑失真。</li>
<li>Workers KV 與關聯產品出現控制操作異常。</li>
<li>團隊透過回退與修正逐步收斂錯誤。</li>
<li>事故後回寫 deployment guardrail、decision log 與 evidence 管線。</li>
</ol>
<h2 id="可回寫控制面">可回寫控制面</h2>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>暴露缺口</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>變更範圍治理</td>
          <td>控制面變更可快速全域擴散</td>
          <td>強制 staged rollout + canary gate</td>
      </tr>
      <tr>
          <td>決策紀錄</td>
          <td>假設與回退條件在事中容易遺失</td>
          <td>強制使用 [8.19] 決策欄位模板</td>
      </tr>
      <tr>
          <td>證據回寫</td>
          <td>教訓停留在事件敘事</td>
          <td>連到 [8.22]，把證據回寫到 observability/reliability 控制面</td>
      </tr>
      <tr>
          <td>規則推送安全閘門</td>
          <td>變更工具缺少風險分級</td>
          <td>回寫 [6.24] 的 rule rollout gate</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>事故決策紀錄： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li>事故證據回寫： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
<li>規則推送安全閘門： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a></li>
<li>觀測治理模型： <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/cloudflare-incident-on-october-30-2023/">Cloudflare incident on October 30, 2023</a></li>
</ul>
]]></content:encoded></item><item><title>營運後技術轉換：語言、工具與架構何時該換</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/</guid><description>&lt;p>這個案例的核心責任是把「營運後轉換」變成可判讀決策，而不是技術潮流追逐。服務在成長期常會遇到早期選型與現況負載不再匹配，此時轉換的重點是風險收斂與效率改善，而不是語言偏好。&lt;/p>
&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>Slack：PHP 逐步遷移到 Hack&lt;/td>
 &lt;td>語言/型別系統&lt;/td>
 &lt;td>以漸進式靜態型別提升重構安全與開發效率，降低 runtime 才暴露型別錯誤的成本。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Discord：Read States 服務 Go 重寫為 Rust&lt;/td>
 &lt;td>語言/執行模型&lt;/td>
 &lt;td>Go 服務在特定負載下出現 GC 造成的週期性延遲尖峰，Rust 以無 GC 記憶體模型降低延遲抖動。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dropbox：Python 2 轉 Python 3&lt;/td>
 &lt;td>語言/runtime 生命週期&lt;/td>
 &lt;td>Python 2 EOL 與型別工具鏈演進壓力，驅動全面升級並降低長期維護風險。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dropbox：內部 RPC 轉向 gRPC（Courier）&lt;/td>
 &lt;td>工具/協定標準化&lt;/td>
 &lt;td>多語言服務擴張後，需要統一傳輸契約、提高跨團隊可維護性與可觀測性。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GitLab：單一資料庫拆成 Main/CI 資料庫&lt;/td>
 &lt;td>資料層架構&lt;/td>
 &lt;td>單庫承載產品與 CI 工作負載，容量與干擾風險上升，需以職責拆分換取穩定性。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Notion：Postgres 單庫轉分片&lt;/td>
 &lt;td>資料層架構&lt;/td>
 &lt;td>寫入與資料量成長造成熱點與容量壓力，以分片提升可擴展性與故障隔離。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify：Rails 後端引入 Vitess 水平擴充&lt;/td>
 &lt;td>資料層工具&lt;/td>
 &lt;td>MySQL 垂直擴充成本上升，需在不中斷服務前提下取得分片與路由能力。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify：Ruby 導入 Sorbet 靜態型別&lt;/td>
 &lt;td>工具/語言治理&lt;/td>
 &lt;td>大型程式碼庫重構與跨團隊協作風險高，需要型別訊號降低變更不確定性。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Figma：服務遷移至 Kubernetes&lt;/td>
 &lt;td>平台/部署工具&lt;/td>
 &lt;td>手工或半自動部署流程難以支撐規模成長，需要統一調度、回滾與資源治理能力。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cloudflare：邊緣系統由 C/NGINX 模組逐步改寫 Rust&lt;/td>
 &lt;td>語言/安全性&lt;/td>
 &lt;td>記憶體安全與可維護性需求提升，在高效能路徑引入 Rust 降低記憶體錯誤風險。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Slack：關鍵服務從單體拓撲遷移到 Cell-based 架構&lt;/td>
 &lt;td>架構/隔離策略&lt;/td>
 &lt;td>以降低爆炸半徑與提高冗餘為目標，將重大故障影響限制在局部 cell。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Uber：大規模微服務治理轉向 Domain-oriented 邊界重整&lt;/td>
 &lt;td>架構/組織對齊&lt;/td>
 &lt;td>服務數量擴張後依賴複雜度暴增，需要把技術邊界與業務邊界對齊以降低協作與故障傳染成本。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meta：MySQL 大規模場景導入 MyRocks&lt;/td>
 &lt;td>儲存引擎/成本優化&lt;/td>
 &lt;td>寫入放大與儲存成本壓力上升，透過新儲存引擎換取空間效率與寫入效能。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例分組判讀">案例分組判讀&lt;/h2>
&lt;h3 id="語言與型別系統轉換">語言與型別系統轉換&lt;/h3>
&lt;p>語言轉換常見於「延遲抖動不可接受」或「重構風險不可接受」兩類壓力。前者多是 runtime/記憶體模型問題，後者多是大型程式碼庫可維護性問題。&lt;/p>
&lt;ul>
&lt;li>代表案例：Slack PHP -&amp;gt; Hack、Discord Go -&amp;gt; Rust、Dropbox Python 2 -&amp;gt; Python 3、Cloudflare C/NGINX -&amp;gt; Rust&lt;/li>
&lt;li>主要動機：降低 tail latency、提升記憶體安全、對抗 runtime EOL、引入更強型別訊號&lt;/li>
&lt;/ul>
&lt;h3 id="資料層與儲存架構轉換">資料層與儲存架構轉換&lt;/h3>
&lt;p>資料層轉換通常源自單體資料庫在容量、隔離與可恢復性上出現結構性瓶頸，追新技術本身很少是真正驅動力。&lt;/p>
&lt;ul>
&lt;li>代表案例：GitLab Main/CI split、Notion Postgres sharding、Shopify Vitess、Meta MyRocks&lt;/li>
&lt;li>主要動機：解耦不同負載、降低熱點、取得水平擴充、降低儲存成本&lt;/li>
&lt;/ul>
&lt;h3 id="平台與部署工具轉換">平台與部署工具轉換&lt;/h3>
&lt;p>平台轉換通常發生在部署頻率提升後，原本的人工作業或弱自動化無法承擔發布風險。&lt;/p>
&lt;ul>
&lt;li>代表案例：Figma 遷移 Kubernetes、Dropbox RPC 標準化到 gRPC&lt;/li>
&lt;li>主要動機：統一部署控制面、縮短發布/回滾時間、提升跨語言協作效率&lt;/li>
&lt;/ul>
&lt;h3 id="架構邊界重整">架構邊界重整&lt;/h3>
&lt;p>架構重整通常是「故障會跨邊界放大」或「團隊邊界與系統邊界失配」時的修正動作。&lt;/p>
&lt;ul>
&lt;li>代表案例：Slack cellular architecture、Uber domain-oriented microservice governance&lt;/li>
&lt;li>主要動機：縮小 blast radius、讓服務責任與組織責任對齊、降低跨團隊耦合&lt;/li>
&lt;/ul>
&lt;h2 id="三倍擴充案例池42">三倍擴充案例池（42）&lt;/h2>
&lt;p>這份案例池的核心責任是提供「可直接回寫實作」的案例母體，而不是只做公司清單。下面分成兩層：外部官方遷移案例（偏選型與轉換動機）與站內已整理案例（偏實作、驗證、事故教訓）。&lt;/p>
&lt;h3 id="a-外部官方遷移案例20">A. 外部官方遷移案例（20）&lt;/h3>
&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>Slack PHP -&amp;gt; Hack&lt;/td>
 &lt;td>漸進型別化與大型重構安全&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Discord Go -&amp;gt; Rust&lt;/td>
 &lt;td>延遲長尾與 GC 抖動治理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dropbox Python 2 -&amp;gt; 3&lt;/td>
 &lt;td>runtime EOL 與生態升級&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dropbox RPC -&amp;gt; gRPC&lt;/td>
 &lt;td>協定標準化與跨語言維運&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GitLab Main/CI DB split&lt;/td>
 &lt;td>單庫拆分與負載隔離&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Notion Postgres sharding&lt;/td>
 &lt;td>熱點與容量壓力分片&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify MySQL -&amp;gt; Vitess&lt;/td>
 &lt;td>水平擴充與線上遷移&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify Ruby + Sorbet&lt;/td>
 &lt;td>動態語言型別治理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Figma -&amp;gt; Kubernetes&lt;/td>
 &lt;td>部署控制面平台化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cloudflare C/NGINX -&amp;gt; Rust&lt;/td>
 &lt;td>記憶體安全與效能路徑重寫&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Slack monolith topology -&amp;gt; cellular&lt;/td>
 &lt;td>blast radius 局部化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Uber domain-oriented microservices&lt;/td>
 &lt;td>服務邊界與組織對齊&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/service-capability-map/" data-link-title="0.1 後端服務能力地圖" data-link-desc="用需求類型判斷應先評估資料庫、快取、訊息佇列、觀測平台或部署平台">0.1&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meta MySQL -&amp;gt; MyRocks&lt;/td>
 &lt;td>儲存成本與寫入效率&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pinterest HBase -&amp;gt; TiDB&lt;/td>
 &lt;td>零停機儲存遷移&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pinterest 新 wide-column DB（RocksDB）&lt;/td>
 &lt;td>資料層能力換血&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meta MySQL Raft deploy&lt;/td>
 &lt;td>failover 工具化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify MySQL upgrade program&lt;/td>
 &lt;td>大規模升級治理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GitLab major PostgreSQL upgrade&lt;/td>
 &lt;td>主版本升級與回退窗&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS shuffle sharding adoption&lt;/td>
 &lt;td>多租戶隔離重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cloudflare observability stack內建化&lt;/td>
 &lt;td>觀測平台內生化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="b-站內可回寫實作案例池22">B. 站內可回寫實作案例池（22）&lt;/h3>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe：Idempotency 與零停機遷移&lt;/a>&lt;/td>
 &lt;td>交易安全 + migration 並行&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest：快取可靠性與容量驚奇治理&lt;/a>&lt;/td>
 &lt;td>快取策略與容量重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon：Shuffle Sharding 與 Cell 邊界&lt;/a>&lt;/td>
 &lt;td>cell/shard 重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">Meta：Region Failover 與可靠性邊界&lt;/a>&lt;/td>
 &lt;td>區域切換能力演進&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify：BFCM 容量治理與 Game Day&lt;/a>&lt;/td>
 &lt;td>高峰前治理轉換&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google：Error Budget 發布門檻&lt;/a>&lt;/td>
 &lt;td>從速度導向轉為預算導向&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">Microsoft：變更治理與可靠性門檻&lt;/a>&lt;/td>
 &lt;td>變更流程平台化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">Spotify：平台工程與可靠性契約&lt;/a>&lt;/td>
 &lt;td>團隊自助平台化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">LinkedIn：Capacity Headroom 與 On-call 分層&lt;/a>&lt;/td>
 &lt;td>容量與值班模型重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">Netflix：Steady State、Chaos 與 FIT&lt;/a>&lt;/td>
 &lt;td>驗證方法轉換&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">6.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">Honeycomb：Burn Rate 驅動操作&lt;/a>&lt;/td>
 &lt;td>告警治理轉換&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">GitHub 2018 MySQL Topology Incident&lt;/a>&lt;/td>
 &lt;td>跨區 DB 拓撲決策轉換&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">Reddit 2023 Kubernetes 升級事故&lt;/a>&lt;/td>
 &lt;td>平台升級失敗模式&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">Discord 2022 Gateway 容量事件&lt;/a>&lt;/td>
 &lt;td>容量與連線模型調整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">Cloudflare 2019 Regex CPU Outage&lt;/a>&lt;/td>
 &lt;td>規則系統推送模型調整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-workflow-automation-boundary/" data-link-title="8.21 Incident Workflow Automation Boundary" data-link-desc="定義哪些事故流程適合自動化，哪些決策需要保留人工確認">8.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">Cloudflare 2023 Control Plane Token Incident&lt;/a>&lt;/td>
 &lt;td>控制面信任邊界重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-control-handoff-to-delivery-and-incident/" data-link-title="7.18 資安控制面如何交接到部署與事故流程" data-link-desc="建立資安控制面交接到部署、可靠性與事故流程的大綱">7.12&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">Fastly 2021 全域 Edge 配置事故&lt;/a>&lt;/td>
 &lt;td>配置發布流程轉換&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">AWS S3 2017 US-EAST-1 事件&lt;/a>&lt;/td>
 &lt;td>控制面操作模型重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">Atlassian 2022 多租戶刪除事故&lt;/a>&lt;/td>
 &lt;td>tenant 安全邊界重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">Azure AD 2021 身分控制面事件&lt;/a>&lt;/td>
 &lt;td>身分服務依賴治理&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">GCP 2019 多服務網路擁塞事件&lt;/a>&lt;/td>
 &lt;td>區域網路依賴重整&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">Heroku 2021 Routing 控制事件&lt;/a>&lt;/td>
 &lt;td>路由控制面恢復策略&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這兩層合計 42 個案例。使用方式是先在 A 層找轉換動機，再到 B 層找可操作證據與失敗模式，最後回寫到 &lt;code>01/04/06/08&lt;/code> 的正文。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是把「營運後轉換」變成可判讀決策，而不是技術潮流追逐。服務在成長期常會遇到早期選型與現況負載不再匹配，此時轉換的重點是風險收斂與效率改善，而不是語言偏好。</p>
<h2 id="大量真實案例與轉換原因">大量真實案例與轉換原因</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>轉換類型</th>
          <th>為什麼轉換</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Slack：PHP 逐步遷移到 Hack</td>
          <td>語言/型別系統</td>
          <td>以漸進式靜態型別提升重構安全與開發效率，降低 runtime 才暴露型別錯誤的成本。</td>
      </tr>
      <tr>
          <td>Discord：Read States 服務 Go 重寫為 Rust</td>
          <td>語言/執行模型</td>
          <td>Go 服務在特定負載下出現 GC 造成的週期性延遲尖峰，Rust 以無 GC 記憶體模型降低延遲抖動。</td>
      </tr>
      <tr>
          <td>Dropbox：Python 2 轉 Python 3</td>
          <td>語言/runtime 生命週期</td>
          <td>Python 2 EOL 與型別工具鏈演進壓力，驅動全面升級並降低長期維護風險。</td>
      </tr>
      <tr>
          <td>Dropbox：內部 RPC 轉向 gRPC（Courier）</td>
          <td>工具/協定標準化</td>
          <td>多語言服務擴張後，需要統一傳輸契約、提高跨團隊可維護性與可觀測性。</td>
      </tr>
      <tr>
          <td>GitLab：單一資料庫拆成 Main/CI 資料庫</td>
          <td>資料層架構</td>
          <td>單庫承載產品與 CI 工作負載，容量與干擾風險上升，需以職責拆分換取穩定性。</td>
      </tr>
      <tr>
          <td>Notion：Postgres 單庫轉分片</td>
          <td>資料層架構</td>
          <td>寫入與資料量成長造成熱點與容量壓力，以分片提升可擴展性與故障隔離。</td>
      </tr>
      <tr>
          <td>Shopify：Rails 後端引入 Vitess 水平擴充</td>
          <td>資料層工具</td>
          <td>MySQL 垂直擴充成本上升，需在不中斷服務前提下取得分片與路由能力。</td>
      </tr>
      <tr>
          <td>Shopify：Ruby 導入 Sorbet 靜態型別</td>
          <td>工具/語言治理</td>
          <td>大型程式碼庫重構與跨團隊協作風險高，需要型別訊號降低變更不確定性。</td>
      </tr>
      <tr>
          <td>Figma：服務遷移至 Kubernetes</td>
          <td>平台/部署工具</td>
          <td>手工或半自動部署流程難以支撐規模成長，需要統一調度、回滾與資源治理能力。</td>
      </tr>
      <tr>
          <td>Cloudflare：邊緣系統由 C/NGINX 模組逐步改寫 Rust</td>
          <td>語言/安全性</td>
          <td>記憶體安全與可維護性需求提升，在高效能路徑引入 Rust 降低記憶體錯誤風險。</td>
      </tr>
      <tr>
          <td>Slack：關鍵服務從單體拓撲遷移到 Cell-based 架構</td>
          <td>架構/隔離策略</td>
          <td>以降低爆炸半徑與提高冗餘為目標，將重大故障影響限制在局部 cell。</td>
      </tr>
      <tr>
          <td>Uber：大規模微服務治理轉向 Domain-oriented 邊界重整</td>
          <td>架構/組織對齊</td>
          <td>服務數量擴張後依賴複雜度暴增，需要把技術邊界與業務邊界對齊以降低協作與故障傳染成本。</td>
      </tr>
      <tr>
          <td>Meta：MySQL 大規模場景導入 MyRocks</td>
          <td>儲存引擎/成本優化</td>
          <td>寫入放大與儲存成本壓力上升，透過新儲存引擎換取空間效率與寫入效能。</td>
      </tr>
  </tbody>
</table>
<h2 id="案例分組判讀">案例分組判讀</h2>
<h3 id="語言與型別系統轉換">語言與型別系統轉換</h3>
<p>語言轉換常見於「延遲抖動不可接受」或「重構風險不可接受」兩類壓力。前者多是 runtime/記憶體模型問題，後者多是大型程式碼庫可維護性問題。</p>
<ul>
<li>代表案例：Slack PHP -&gt; Hack、Discord Go -&gt; Rust、Dropbox Python 2 -&gt; Python 3、Cloudflare C/NGINX -&gt; Rust</li>
<li>主要動機：降低 tail latency、提升記憶體安全、對抗 runtime EOL、引入更強型別訊號</li>
</ul>
<h3 id="資料層與儲存架構轉換">資料層與儲存架構轉換</h3>
<p>資料層轉換通常源自單體資料庫在容量、隔離與可恢復性上出現結構性瓶頸，追新技術本身很少是真正驅動力。</p>
<ul>
<li>代表案例：GitLab Main/CI split、Notion Postgres sharding、Shopify Vitess、Meta MyRocks</li>
<li>主要動機：解耦不同負載、降低熱點、取得水平擴充、降低儲存成本</li>
</ul>
<h3 id="平台與部署工具轉換">平台與部署工具轉換</h3>
<p>平台轉換通常發生在部署頻率提升後，原本的人工作業或弱自動化無法承擔發布風險。</p>
<ul>
<li>代表案例：Figma 遷移 Kubernetes、Dropbox RPC 標準化到 gRPC</li>
<li>主要動機：統一部署控制面、縮短發布/回滾時間、提升跨語言協作效率</li>
</ul>
<h3 id="架構邊界重整">架構邊界重整</h3>
<p>架構重整通常是「故障會跨邊界放大」或「團隊邊界與系統邊界失配」時的修正動作。</p>
<ul>
<li>代表案例：Slack cellular architecture、Uber domain-oriented microservice governance</li>
<li>主要動機：縮小 blast radius、讓服務責任與組織責任對齊、降低跨團隊耦合</li>
</ul>
<h2 id="三倍擴充案例池42">三倍擴充案例池（42）</h2>
<p>這份案例池的核心責任是提供「可直接回寫實作」的案例母體，而不是只做公司清單。下面分成兩層：外部官方遷移案例（偏選型與轉換動機）與站內已整理案例（偏實作、驗證、事故教訓）。</p>
<h3 id="a-外部官方遷移案例20">A. 外部官方遷移案例（20）</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>轉換主題</th>
          <th>實作討論入口</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Slack PHP -&gt; Hack</td>
          <td>漸進型別化與大型重構安全</td>
          <td><a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6</a></td>
      </tr>
      <tr>
          <td>Discord Go -&gt; Rust</td>
          <td>延遲長尾與 GC 抖動治理</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td>Dropbox Python 2 -&gt; 3</td>
          <td>runtime EOL 與生態升級</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>Dropbox RPC -&gt; gRPC</td>
          <td>協定標準化與跨語言維運</td>
          <td><a href="/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4</a></td>
      </tr>
      <tr>
          <td>GitLab Main/CI DB split</td>
          <td>單庫拆分與負載隔離</td>
          <td><a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6</a></td>
      </tr>
      <tr>
          <td>Notion Postgres sharding</td>
          <td>熱點與容量壓力分片</td>
          <td><a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5</a></td>
      </tr>
      <tr>
          <td>Shopify MySQL -&gt; Vitess</td>
          <td>水平擴充與線上遷移</td>
          <td><a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6</a></td>
      </tr>
      <tr>
          <td>Shopify Ruby + Sorbet</td>
          <td>動態語言型別治理</td>
          <td><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10</a></td>
      </tr>
      <tr>
          <td>Figma -&gt; Kubernetes</td>
          <td>部署控制面平台化</td>
          <td><a href="/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4</a></td>
      </tr>
      <tr>
          <td>Cloudflare C/NGINX -&gt; Rust</td>
          <td>記憶體安全與效能路徑重寫</td>
          <td><a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6</a></td>
      </tr>
      <tr>
          <td>Slack monolith topology -&gt; cellular</td>
          <td>blast radius 局部化</td>
          <td><a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a></td>
      </tr>
      <tr>
          <td>Uber domain-oriented microservices</td>
          <td>服務邊界與組織對齊</td>
          <td><a href="/blog/backend/00-service-selection/service-capability-map/" data-link-title="0.1 後端服務能力地圖" data-link-desc="用需求類型判斷應先評估資料庫、快取、訊息佇列、觀測平台或部署平台">0.1</a></td>
      </tr>
      <tr>
          <td>Meta MySQL -&gt; MyRocks</td>
          <td>儲存成本與寫入效率</td>
          <td><a href="/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2</a></td>
      </tr>
      <tr>
          <td>Pinterest HBase -&gt; TiDB</td>
          <td>零停機儲存遷移</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td>Pinterest 新 wide-column DB（RocksDB）</td>
          <td>資料層能力換血</td>
          <td><a href="/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2</a></td>
      </tr>
      <tr>
          <td>Meta MySQL Raft deploy</td>
          <td>failover 工具化</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a></td>
      </tr>
      <tr>
          <td>Shopify MySQL upgrade program</td>
          <td>大規模升級治理</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>GitLab major PostgreSQL upgrade</td>
          <td>主版本升級與回退窗</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td>AWS shuffle sharding adoption</td>
          <td>多租戶隔離重整</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>Cloudflare observability stack內建化</td>
          <td>觀測平台內生化</td>
          <td><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a></td>
      </tr>
  </tbody>
</table>
<h3 id="b-站內可回寫實作案例池22">B. 站內可回寫實作案例池（22）</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>轉換主題</th>
          <th>實作討論入口</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe：Idempotency 與零停機遷移</a></td>
          <td>交易安全 + migration 並行</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest：快取可靠性與容量驚奇治理</a></td>
          <td>快取策略與容量重整</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon：Shuffle Sharding 與 Cell 邊界</a></td>
          <td>cell/shard 重整</td>
          <td><a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">Meta：Region Failover 與可靠性邊界</a></td>
          <td>區域切換能力演進</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify：BFCM 容量治理與 Game Day</a></td>
          <td>高峰前治理轉換</td>
          <td><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.6</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google：Error Budget 發布門檻</a></td>
          <td>從速度導向轉為預算導向</td>
          <td><a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.2</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">Microsoft：變更治理與可靠性門檻</a></td>
          <td>變更流程平台化</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">Spotify：平台工程與可靠性契約</a></td>
          <td>團隊自助平台化</td>
          <td><a href="/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">LinkedIn：Capacity Headroom 與 On-call 分層</a></td>
          <td>容量與值班模型重整</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">Netflix：Steady State、Chaos 與 FIT</a></td>
          <td>驗證方法轉換</td>
          <td><a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">6.5</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">Honeycomb：Burn Rate 驅動操作</a></td>
          <td>告警治理轉換</td>
          <td><a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.13</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">GitHub 2018 MySQL Topology Incident</a></td>
          <td>跨區 DB 拓撲決策轉換</td>
          <td><a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">Reddit 2023 Kubernetes 升級事故</a></td>
          <td>平台升級失敗模式</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">Discord 2022 Gateway 容量事件</a></td>
          <td>容量與連線模型調整</td>
          <td><a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">Cloudflare 2019 Regex CPU Outage</a></td>
          <td>規則系統推送模型調整</td>
          <td><a href="/blog/backend/08-incident-response/incident-workflow-automation-boundary/" data-link-title="8.21 Incident Workflow Automation Boundary" data-link-desc="定義哪些事故流程適合自動化，哪些決策需要保留人工確認">8.13</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">Cloudflare 2023 Control Plane Token Incident</a></td>
          <td>控制面信任邊界重整</td>
          <td><a href="/blog/backend/07-security-data-protection/security-control-handoff-to-delivery-and-incident/" data-link-title="7.18 資安控制面如何交接到部署與事故流程" data-link-desc="建立資安控制面交接到部署、可靠性與事故流程的大綱">7.12</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">Fastly 2021 全域 Edge 配置事故</a></td>
          <td>配置發布流程轉換</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">AWS S3 2017 US-EAST-1 事件</a></td>
          <td>控制面操作模型重整</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">Atlassian 2022 多租戶刪除事故</a></td>
          <td>tenant 安全邊界重整</td>
          <td><a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">Azure AD 2021 身分控制面事件</a></td>
          <td>身分服務依賴治理</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">GCP 2019 多服務網路擁塞事件</a></td>
          <td>區域網路依賴重整</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">Heroku 2021 Routing 控制事件</a></td>
          <td>路由控制面恢復策略</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<p>這兩層合計 42 個案例。使用方式是先在 A 層找轉換動機，再到 B 層找可操作證據與失敗模式，最後回寫到 <code>01/04/06/08</code> 的正文。</p>
<h2 id="跨分類覆蓋與缺口">跨分類覆蓋與缺口</h2>
<p>這一段的核心責任是避免案例池被資料庫議題主導。選型與轉換在實務上會同時涉及快取、訊息傳遞、觀測、部署、安全與事故治理，因此案例覆蓋要跨分類配置。</p>
<table>
  <thead>
      <tr>
          <th>分類</th>
          <th>目前案例密度</th>
          <th>代表案例入口</th>
          <th>目前缺口與補查方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>01 Database / Storage</td>
          <td>高</td>
          <td><a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據</a></td>
          <td>已有遷移流程與 rollout evidence；下一步補更多 vendor 轉換對照</td>
      </tr>
      <tr>
          <td>02 Cache / Redis</td>
          <td>中低</td>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest：快取可靠性與容量驚奇治理</a></td>
          <td>補「快取策略轉換」案例（cache-aside -&gt; write-through、multi-layer cache）</td>
      </tr>
      <tr>
          <td>03 Message Queue</td>
          <td>中低</td>
          <td><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon：Shuffle Sharding 與 Cell 邊界</a></td>
          <td>補「自管 broker -&gt; managed queue」與「語義轉換（at-least-once / exactly-once）」</td>
      </tr>
      <tr>
          <td>04 Observability</td>
          <td>中</td>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">Honeycomb：Burn Rate 驅動操作</a></td>
          <td>補「監控平台遷移」與「OpenTelemetry 導入遷移」案例</td>
      </tr>
      <tr>
          <td>05 Deployment Platform</td>
          <td>中</td>
          <td><a href="/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">Reddit：2023 Kubernetes 升級事故</a></td>
          <td>補「自建部署 -&gt; Kubernetes/GitOps」轉換案例</td>
      </tr>
      <tr>
          <td>06 Reliability</td>
          <td>高</td>
          <td><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe：Idempotency 與零停機遷移</a></td>
          <td>持續補不同產業的 rollout/rollback 對照</td>
      </tr>
      <tr>
          <td>07 Security / Data Protection</td>
          <td>中低</td>
          <td><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">Cloudflare 2023 Control Plane Token Incident</a></td>
          <td>補「憑證、金鑰、身分邊界治理轉換」案例</td>
      </tr>
      <tr>
          <td>08 Incident Response</td>
          <td>高</td>
          <td><a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">GitHub 2018 MySQL Topology Incident</a></td>
          <td>補「轉換期間事故」專題，建立遷移失敗模式索引</td>
      </tr>
  </tbody>
</table>
<h2 id="覆蓋門檻與缺口追蹤">覆蓋門檻與缺口追蹤</h2>
<p>這份追蹤表的核心責任是把「案例夠不夠」變成可量化判斷，而不是主觀感覺。</p>
<table>
  <thead>
      <tr>
          <th>分類</th>
          <th>最低門檻（篇）</th>
          <th>目前已收錄（篇）</th>
          <th>缺口（篇）</th>
          <th>狀態</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>01 Database / Storage</td>
          <td>12</td>
          <td>12</td>
          <td>0</td>
          <td>達標</td>
          <td>補 vendor 轉換對照深度</td>
      </tr>
      <tr>
          <td>02 Cache / Redis</td>
          <td>10</td>
          <td>10</td>
          <td>0</td>
          <td>達標</td>
          <td>進入案例深度擴寫與反例補充</td>
      </tr>
      <tr>
          <td>03 Message Queue</td>
          <td>10</td>
          <td>10</td>
          <td>0</td>
          <td>達標</td>
          <td>進入案例深度擴寫與反例補充</td>
      </tr>
      <tr>
          <td>04 Observability</td>
          <td>10</td>
          <td>10</td>
          <td>0</td>
          <td>達標</td>
          <td>進入案例深度擴寫與反例補充</td>
      </tr>
      <tr>
          <td>05 Deployment Platform</td>
          <td>10</td>
          <td>10</td>
          <td>0</td>
          <td>達標</td>
          <td>進入案例深度擴寫與反例補充</td>
      </tr>
      <tr>
          <td>06 Reliability</td>
          <td>10</td>
          <td>12</td>
          <td>0</td>
          <td>達標</td>
          <td>補產業多樣性與 rollback 成本對照</td>
      </tr>
      <tr>
          <td>07 Security / Data Protection</td>
          <td>10</td>
          <td>10</td>
          <td>0</td>
          <td>達標</td>
          <td>進入案例深度擴寫與反例補充</td>
      </tr>
      <tr>
          <td>08 Incident Response</td>
          <td>10</td>
          <td>12</td>
          <td>0</td>
          <td>達標</td>
          <td>補「轉換期間事故」專題索引</td>
      </tr>
  </tbody>
</table>
<h2 id="下一輪優先順序">下一輪優先順序</h2>
<p>門檻已達標，下一輪優先順序改為：</p>
<ol>
<li>每分類補「失敗反例」與「轉換失敗回退案例」</li>
<li>每分類補「同議題不同規模企業」對照</li>
<li>把案例回寫到章節正文中的判讀訊號與 tripwire 欄位</li>
</ol>
<h2 id="回退失敗專題索引">回退失敗專題索引</h2>
<p>這個索引的核心責任是讓讀者在「已經出錯」時，能快速找到對應回退失敗模式，而不是從頭重讀選型章節。</p>
<table>
  <thead>
      <tr>
          <th>分類</th>
          <th>回退失敗專題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>02 Cache / Redis</td>
          <td><a href="/blog/backend/02-cache-redis/cases/failure-cache-stampede-rollout-regression/" data-link-title="2.C9 反例：快取切換引發 Stampede 回歸" data-link-desc="快取策略切換若缺乏保護，會導致回源壓力與錯誤率連鎖上升。">2.C9 反例：快取切換失敗</a></td>
      </tr>
      <tr>
          <td>03 Message Queue</td>
          <td><a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義切換失敗</a></td>
      </tr>
      <tr>
          <td>04 Observability</td>
          <td><a href="/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/" data-link-title="4.C9 反例：OTel 遷移後訊號漂移" data-link-desc="雙軌採集未對齊導致告警與 SLO 判讀失真。">4.C9 反例：OTel 訊號漂移</a></td>
      </tr>
      <tr>
          <td>05 Deployment Platform</td>
          <td><a href="/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9 反例：切流未先 drain</a></td>
      </tr>
      <tr>
          <td>07 Security / Data Protection</td>
          <td><a href="/blog/backend/07-security-data-protection/cases/failure-credential-rotation-without-scope/" data-link-title="7.C9 反例：憑證輪替未分 Scope" data-link-desc="憑證輪替若未分域分批，容易造成跨系統連鎖中斷。">7.C9 反例：憑證輪替失敗</a></td>
      </tr>
  </tbody>
</table>
<h2 id="回退判讀寫法">回退判讀寫法</h2>
<p>回退判讀的核心責任是把失敗條件寫回該分類自己的業務語境。快取看的是回源壓力與資料新鮮度；queue 看的是語義、lag 與重播；observability 看的是訊號語意漂移；deployment 看的是切流、draining 與連線生命週期；security 看的是身份、憑證作用域與控制面擴散。</p>
<p>這些判讀不能抽成同一份模板。每次寫案例時，先回答該分類自己的問題：哪個業務路徑受影響、哪個訊號最早失真、哪個回退動作會降低傷害、哪份證據能證明回退有效。</p>
<h2 id="下一輪補查清單非-db-優先">下一輪補查清單（非 DB 優先）</h2>
<p>下一輪補查會優先補目前中低密度分類，目標是讓每一類至少有 8 到 12 個可回寫案例。</p>
<ol>
<li>Cache：快取策略遷移與失效治理（multi-layer、eviction、warmup）</li>
<li>Queue：broker/語義轉換與 replay 風險控制</li>
<li>Observability：監控平台遷移與資料品質治理</li>
<li>Deployment：部署平台轉換與灰度/回滾策略</li>
<li>Security：控制面信任邊界與憑證機制轉換</li>
</ol>
<h2 id="第二批外部案例補充非-db-類">第二批外部案例補充（非 DB 類）</h2>
<p>這一批的核心責任是把中低密度分類補到可用水位，讓 <code>02/03/04/05/07</code> 都有可引用的真實轉換案例，而不是只有資料庫案例可用。</p>
<table>
  <thead>
      <tr>
          <th>分類</th>
          <th>案例</th>
          <th>轉換焦點</th>
          <th>回寫入口</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cache</td>
          <td>Meta：Cache made consistent</td>
          <td>cache invalidation 一致性治理升級</td>
          <td><a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.1</a></td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>Meta：mcrouter at scale</td>
          <td>單機快取轉成跨區路由層</td>
          <td><a href="/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.4</a></td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>Meta：CacheLib + Kangaroo</td>
          <td>DRAM-only 快取轉向 flash-friendly 架構</td>
          <td><a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.5</a></td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>Shopify：Marshal -&gt; MessagePack cache migration</td>
          <td>快取序列化格式遷移與雙軌相容</td>
          <td><a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.1</a></td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>Shopify：Shop App write-through cache</td>
          <td>read-heavy 路徑轉 write-through</td>
          <td><a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.1</a></td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>Meta：FOQS disaster-ready migration</td>
          <td>區域佇列轉全域架構且零停機</td>
          <td><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.3</a></td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>LinkedIn：Running Kafka at Scale</td>
          <td>單叢集使用模式轉 tiered cluster</td>
          <td><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1</a></td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>LinkedIn：TopicGC</td>
          <td>Kafka topic 治理從手動轉自動回收</td>
          <td><a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.2</a></td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>VMware Tanzu CloudHealth：Kafka -&gt; Amazon MSK</td>
          <td>自管 broker 轉 managed streaming</td>
          <td><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1</a></td>
      </tr>
      <tr>
          <td>Queue</td>
          <td>Slack：Scaling job queue</td>
          <td>背景工作通道轉 Kafka + Redis 組合</td>
          <td><a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.4</a></td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>AWS：X-Ray SDK/Daemon -&gt; OpenTelemetry migration</td>
          <td>vendor SDK 轉 OTel 標準化</td>
          <td><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.21</a></td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>Google Cloud：OTLP support in Cloud Trace (2025)</td>
          <td>專有 ingest 轉 OTLP 標準入口</td>
          <td><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.21</a></td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>AWS：ADOT 建立集中觀測平台</td>
          <td>多代理轉單一 OTel pipeline</td>
          <td><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a></td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>AWS：EKS + ADOT + X-Ray/CloudWatch</td>
          <td>既有監控拆散轉標準化管線</td>
          <td><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.7</a></td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>Honeycomb：Burn rate operations</td>
          <td>告警規則轉 error budget 驅動治理</td>
          <td><a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.13</a></td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Tradeshift：self-hosted K8s -&gt; EKS (zero downtime)</td>
          <td>自管控制面轉 managed control plane</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Condé Nast：K8s platform modernization on EKS</td>
          <td>多團隊異質集群轉統一平台</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Orbitera：AWS -&gt; GKE migration</td>
          <td>基礎平台重置與容器編排轉換</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Mobileye：workloads -&gt; EKS</td>
          <td>資源調度模式轉 managed K8s</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Miro：microservices/K8s -&gt; EKS managed</td>
          <td>自維運平台轉 managed service 組合</td>
          <td><a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2</a></td>
      </tr>
      <tr>
          <td>Security/Control Plane</td>
          <td>Cloudflare：2026 route leak incident</td>
          <td>路由政策自動化治理重整</td>
          <td><a href="/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.16</a></td>
      </tr>
      <tr>
          <td>Security/Control Plane</td>
          <td>Cloudflare：2026 BYOIP BGP withdrawal</td>
          <td>控制面變更保護與回退策略</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>Security/Control Plane</td>
          <td>Cloudflare：2023 control-plane token incident</td>
          <td>token 管理邊界與供應鏈信任調整</td>
          <td><a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.11</a></td>
      </tr>
      <tr>
          <td>Security/Control Plane</td>
          <td>Azure AD：2021 identity control-plane disruption</td>
          <td>身分控制面故障隔離與恢復路由</td>
          <td><a href="/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.8</a></td>
      </tr>
      <tr>
          <td>Security/Control Plane</td>
          <td>Microsoft 365：2023 suite-wide authentication incident</td>
          <td>身分服務相依邊界重整</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
  </tbody>
</table>
<h2 id="第二批補查來源">第二批補查來源</h2>
<ul>
<li>Meta：Cache consistency / mcrouter / CacheLib / Kangaroo / FOQS / MyRocks migration</li>
<li>LinkedIn Engineering：Kafka at scale / TopicGC</li>
<li>AWS：CloudHealth Kafka -&gt; MSK、X-Ray -&gt; OTel migration、ADOT/EKS 實務、EKS 遷移案例</li>
<li>Google Cloud：OTLP in Cloud Trace、Orbitera -&gt; GKE</li>
<li>Shopify Engineering：cache serialization migration、write-through cache</li>
<li>Cloudflare Post-mortem：2023/2026 control-plane 與路由事件</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>延遲分布長尾惡化</td>
          <td>是平均值問題還是尖峰問題</td>
          <td><a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5</a></td>
      </tr>
      <tr>
          <td>重構風險持續升高</td>
          <td>型別/契約是否不足以支撐變更</td>
          <td><a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6</a></td>
      </tr>
      <tr>
          <td>故障常跨服務放大</td>
          <td>架構邊界是否缺乏隔離能力</td>
          <td><a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a></td>
      </tr>
      <tr>
          <td>發布節奏被品質問題拖慢</td>
          <td>問題在語言、工具鏈或架構層</td>
          <td><a href="/blog/backend/00-service-selection/operations-platform-selection/" data-link-title="0.4 操作平台選型" data-link-desc="區分 log、metric、trace、dashboard、alert、deployment 與 reliability 的選型邊界">0.4</a></td>
      </tr>
  </tbody>
</table>
<h2 id="轉換決策資料要求">轉換決策資料要求</h2>
<table>
  <thead>
      <tr>
          <th>資料面向</th>
          <th>最低需要的證據</th>
          <th>若缺失會發生什麼事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>成本面</td>
          <td>現況維運成本與轉換成本（人力、基礎設施、機會成本）</td>
          <td>轉換中途停擺或 ROI 判斷失真</td>
      </tr>
      <tr>
          <td>風險面</td>
          <td>故障型態、爆炸半徑、回退時間</td>
          <td>上線後故障放大但無法快速止血</td>
      </tr>
      <tr>
          <td>性能面</td>
          <td>P50/P95/P99、吞吐、尖峰流量下的行為</td>
          <td>只優化平均值，長尾問題仍存在</td>
      </tr>
      <tr>
          <td>組織面</td>
          <td>團隊技能分布、訓練成本、維運責任邊界</td>
          <td>工具換了但組織無法承接</td>
      </tr>
      <tr>
          <td>生命週期面</td>
          <td>依賴版本 EOL、供應商策略、平台相容性</td>
          <td>被動升級，且在最差時機被迫遷移</td>
      </tr>
      <tr>
          <td>遷移可行性面</td>
          <td>雙寫/雙跑策略、灰度範圍、指標切換門檻、回滾條件</td>
          <td>遷移無法分段驗證，風險一次性爆發</td>
      </tr>
  </tbody>
</table>
<h2 id="轉換前要先回答的三個問題">轉換前要先回答的三個問題</h2>
<ol>
<li>現有問題是「局部優化可解」還是「結構性不匹配」？</li>
<li>轉換後的收益是性能、可靠性、開發效率哪一項，如何量化？</li>
<li>遷移期間如何維持雙軌可運行與回退能力？</li>
</ol>
<p>如果三個問題答不清楚，通常代表先做局部治理比全面轉換更穩定。</p>
<h2 id="常見誤區">常見誤區</h2>
<p>把「技術新舊」當成轉換理由，容易忽略遷移期成本。可靠做法是先界定症狀與邊界，再決定要換語言、換工具，或只換架構切分方式。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>若問題在執行時特性（延遲抖動、記憶體模型），先回 <a href="/blog/backend/00-service-selection/state-storage-selection/" data-link-title="0.2 狀態與資料儲存選型" data-link-desc="區分 source of truth、快取、搜尋索引、event log 與 object storage 的選型邊界">0.2</a> 與 <a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">0.5</a>。若是資料庫轉換已進入執行階段，直接進 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">1.6 資料庫轉換實作</a>；需要把 production migration 寫成 evidence、gate 與 decision log，接 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據</a>；需要放行與回滾治理時，接 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 Migration Safety</a>；若要看事故層教訓，接 <a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">GitHub 2018 Oct21 MySQL Topology Incident</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://slack.engineering/hacklang-at-slack-a-better-php/">Hacklang at Slack: A Better PHP</a>：Slack 說明 PHP 到 Hack 的遷移動機與型別收益。</li>
<li><a href="https://slack.engineering/how-big-technical-changes-happen-at-slack/">How Big Technical Changes Happen at Slack</a>：Slack 逐步遷移與組織推進方式。</li>
<li><a href="https://discord.com/blog/why-discord-is-switching-from-go-to-rust">Why Discord is switching from Go to Rust</a>：Discord 說明 Go→Rust 的延遲與 GC 觀察。</li>
<li><a href="https://slack.engineering/slacks-migration-to-a-cellular-architecture/">Slack’s Migration to a Cellular Architecture</a>：Slack 從單體拓撲轉到 cell 架構的原因。</li>
<li><a href="https://dropbox.tech/application/the-long-awaited-python-3-upgrade-at-dropbox">The Long-Awaited Python 3 Upgrade at Dropbox</a>：Dropbox 的 Python 2 -&gt; 3 遷移動機與推進方式。</li>
<li><a href="https://dropbox.tech/infrastructure/rewriting-the-heart-of-our-sync-engine">Rewriting the heart of our sync engine</a>：Dropbox 在核心效能路徑重寫的轉換決策脈絡。</li>
<li><a href="https://dropbox.tech/infrastructure/courier-driving-the-first-years-of-grpc">Courier: Driving the first years of gRPC</a>：Dropbox 內部 RPC 到 gRPC 的演進背景。</li>
<li><a href="https://about.gitlab.com/blog/2022/06/02/splitting-database-into-main-and-ci/">Splitting database into Main and CI</a>：GitLab 的資料庫職責拆分案例。</li>
<li><a href="https://www.notion.com/blog/sharding-postgres-at-notion">Sharding Postgres at Notion</a>：Notion 分片遷移與容量壓力背景。</li>
<li><a href="https://shopify.engineering/blogs/engineering/horizontally-scaling-the-rails-backend-of-shop-app-with-vitess">Horizontally scaling the Rails backend of Shop App with Vitess</a>：Shopify 導入 Vitess 的原因與方式。</li>
<li><a href="https://shopify.engineering/adopting-sorbet">How Shopify Is Adopting Sorbet</a>：Shopify 在大型 Ruby 程式碼庫導入型別系統。</li>
<li><a href="https://www.figma.com/blog/migrating-figma-to-kubernetes/">Migrating Figma to Kubernetes</a>：Figma 的平台遷移原因與收益。</li>
<li><a href="https://blog.cloudflare.com/rust-nginx-module/">A Rust regex engine in NGINX</a>：Cloudflare 在高效能路徑導入 Rust 的案例。</li>
<li><a href="https://www.uber.com/en-GB/blog/microservice-architecture/">Domain-Oriented Microservice Architecture</a>：Uber 在規模化後重整服務邊界。</li>
<li><a href="https://engineering.fb.com/2016/08/31/core-infra/myrocks-a-space-and-write-optimized-mysql-database/">MyRocks: A space- and write-optimized MySQL database</a>：Meta 導入 MyRocks 的成本與效能動機。</li>
</ul>
]]></content:encoded></item><item><title>Google Cloud Platform</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/</guid><description>&lt;p>GCP 是全球 anycast + 強控制面整合的代表、Load Balancer / IAM 失效是全球控制面事故的教學標竿。Google 公開的 post-mortem 包含詳細時間線與技術細節、適合作為事故敘事範本。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>全球控制面失效：IAM / Load Balancer 失效如何擴散到所有地區&lt;/li>
&lt;li>配置變更的 blast radius：staged rollout 為何在 L7 LB 變更上難以實施&lt;/li>
&lt;li>Postmortem 結構：Google PIR 的 timeline / impact / root cause / action items 格式&lt;/li>
&lt;li>跨服務依賴：Cloud SQL / GKE / Cloud Build 之間的隱性耦合&lt;/li>
&lt;/ul>
&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>Incident #20003&lt;/td>
 &lt;td>Cloud IAM 造成多個 GCP 服務受影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident #20001&lt;/td>
 &lt;td>Cloud IAM 區域性事故與連鎖影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>External ALB incident&lt;/td>
 &lt;td>控制面變更 staged rollout 的限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>下游服務退化案例&lt;/td>
 &lt;td>跨產品的 dependency 暴露&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>GCP 這個案例在講的是全球控制面如何把單一變更擴成跨產品事故。讀者先看懂 LB、IAM 與 identity 依賴的責任，再把 status event 當成 postmortem 與容災設計的入口。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 Load Balancer 或 IAM 出現問題時，故障不會只停在單一產品，而會沿著共享控制面擴散到 YouTube、Drive 或其他下游。當變更需要 staged rollout 時，重點不只是慢，而是能否在全球邊界上保留足夠的驗證空間。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否指出事故是發生在 control plane 還是 data plane&lt;/li>
&lt;li>能否把一個 LB 變更的影響範圍說清楚&lt;/li>
&lt;li>能否在 status page 上對應到具體恢復階段&lt;/li>
&lt;li>能否把 identity 依賴視為跨產品風險&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>GCP 這頁和 Azure AD、AWS S3 是同一組「共享控制面」案例，只是 GCP 更強調全球服務整合。讀者若把這頁和 Cloudflare 一起讀，會更容易看出 staged rollout、identity 依賴與全球路由之間的互相牽制。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>Incident #20003 與 #20001 是 Cloud IAM 影響多服務的直接樣本。&lt;/li>
&lt;li>External ALB incident 顯示全球控制面變更為何需要保留驗證空間。&lt;/li>
&lt;li>LB、IAM 與 identity 依賴是同一條控制面鏈上的不同節點。&lt;/li>
&lt;li>這類樣本適合和 Cloudflare / AWS S3 一起看。&lt;/li>
&lt;li>staged rollout 限制讓 global LB 變更不能只靠局部驗證。&lt;/li>
&lt;li>identity 控制面失效會把下游產品一起拉進事故。&lt;/li>
&lt;li>service health page 的粒度決定客戶能不能快速定位影響範圍。&lt;/li>
&lt;li>global load balancing 讓一個配置錯誤具有跨區同步效應。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://status.cloud.google.com/incident/zall/20003">Google Cloud Status Dashboard: Incident #20003&lt;/a>：Cloud IAM 造成多個 GCP 服務受影響的官方事件摘要。&lt;/li>
&lt;li>&lt;a href="https://status.cloud.google.com/incident/cloud-iam/20001">Google Cloud Status Dashboard: Incident #20001&lt;/a>：Cloud IAM 區域性事故與連鎖影響。&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/architecture/disaster-recovery">Architecting disaster recovery for cloud infrastructure outages&lt;/a>：Google Cloud 的 LB / IAM / IAP / Identity Platform 容災說明。&lt;/li>
&lt;li>&lt;a href="https://status.cloud.google.com/incidents/4jGVd9eWeezcNwH8cFhU">Google Cloud Service Health: External Application Load Balancer incident&lt;/a>：Cloud Load Balancing 的全球影響案例。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>GCP 是全球 anycast + 強控制面整合的代表、Load Balancer / IAM 失效是全球控制面事故的教學標竿。Google 公開的 post-mortem 包含詳細時間線與技術細節、適合作為事故敘事範本。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>全球控制面失效：IAM / Load Balancer 失效如何擴散到所有地區</li>
<li>配置變更的 blast radius：staged rollout 為何在 L7 LB 變更上難以實施</li>
<li>Postmortem 結構：Google PIR 的 timeline / impact / root cause / action items 格式</li>
<li>跨服務依賴：Cloud SQL / GKE / Cloud Build 之間的隱性耦合</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>事件</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Incident #20003</td>
          <td>Cloud IAM 造成多個 GCP 服務受影響</td>
      </tr>
      <tr>
          <td>Incident #20001</td>
          <td>Cloud IAM 區域性事故與連鎖影響</td>
      </tr>
      <tr>
          <td>External ALB incident</td>
          <td>控制面變更 staged rollout 的限制</td>
      </tr>
      <tr>
          <td>下游服務退化案例</td>
          <td>跨產品的 dependency 暴露</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>GCP 這個案例在講的是全球控制面如何把單一變更擴成跨產品事故。讀者先看懂 LB、IAM 與 identity 依賴的責任，再把 status event 當成 postmortem 與容災設計的入口。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 Load Balancer 或 IAM 出現問題時，故障不會只停在單一產品，而會沿著共享控制面擴散到 YouTube、Drive 或其他下游。當變更需要 staged rollout 時，重點不只是慢，而是能否在全球邊界上保留足夠的驗證空間。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否指出事故是發生在 control plane 還是 data plane</li>
<li>能否把一個 LB 變更的影響範圍說清楚</li>
<li>能否在 status page 上對應到具體恢復階段</li>
<li>能否把 identity 依賴視為跨產品風險</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>GCP 這頁和 Azure AD、AWS S3 是同一組「共享控制面」案例，只是 GCP 更強調全球服務整合。讀者若把這頁和 Cloudflare 一起讀，會更容易看出 staged rollout、identity 依賴與全球路由之間的互相牽制。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>Incident #20003 與 #20001 是 Cloud IAM 影響多服務的直接樣本。</li>
<li>External ALB incident 顯示全球控制面變更為何需要保留驗證空間。</li>
<li>LB、IAM 與 identity 依賴是同一條控制面鏈上的不同節點。</li>
<li>這類樣本適合和 Cloudflare / AWS S3 一起看。</li>
<li>staged rollout 限制讓 global LB 變更不能只靠局部驗證。</li>
<li>identity 控制面失效會把下游產品一起拉進事故。</li>
<li>service health page 的粒度決定客戶能不能快速定位影響範圍。</li>
<li>global load balancing 讓一個配置錯誤具有跨區同步效應。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://status.cloud.google.com/incident/zall/20003">Google Cloud Status Dashboard: Incident #20003</a>：Cloud IAM 造成多個 GCP 服務受影響的官方事件摘要。</li>
<li><a href="https://status.cloud.google.com/incident/cloud-iam/20001">Google Cloud Status Dashboard: Incident #20001</a>：Cloud IAM 區域性事故與連鎖影響。</li>
<li><a href="https://cloud.google.com/architecture/disaster-recovery">Architecting disaster recovery for cloud infrastructure outages</a>：Google Cloud 的 LB / IAM / IAP / Identity Platform 容災說明。</li>
<li><a href="https://status.cloud.google.com/incidents/4jGVd9eWeezcNwH8cFhU">Google Cloud Service Health: External Application Load Balancer incident</a>：Cloud Load Balancing 的全球影響案例。</li>
</ul>
]]></content:encoded></item><item><title>Stripe</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/</guid><description>&lt;p>Stripe 是金流場景的可靠性教學標竿、deploy strategy 與 idempotency 設計是 API platform 的工程典範。教學重點在「金流不可重複扣款 / 不可漏扣款」如何透過工程實踐保證。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Deploy strategy：canary / staged rollout 的實作節奏&lt;/li>
&lt;li>Game Day：Stripe 公開的 game day 設計與運作&lt;/li>
&lt;li>Idempotency Key：API 設計層面的 retry safety&lt;/li>
&lt;li>Increasing reliability：從 99% 到 99.999% 的逐階段工程投資&lt;/li>
&lt;li>Capture the flag：內部紅藍演練（這是 Stripe 自有的、不是套 07 的紅藍）&lt;/li>
&lt;/ul>
&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>Idempotency Key&lt;/td>
 &lt;td>API 重試安全的工程實作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Game Day&lt;/td>
 &lt;td>演練設計、scope、後續 action items&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Canary Deploy&lt;/td>
 &lt;td>rollout 節奏、自動 rollback 條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database online migration&lt;/td>
 &lt;td>高頻交易場景的 schema 變更&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Monitoring &amp;amp; Alerting&lt;/td>
 &lt;td>金流場景的訊號設計&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1&lt;/a>&lt;/td>
 &lt;td>Idempotency 與零停機遷移&lt;/td>
 &lt;td>把交易重試與資料遷移放在同一套一致性安全模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">S2&lt;/a>&lt;/td>
 &lt;td>Canary Deploy 與 Progressive Rollout&lt;/td>
 &lt;td>用交易指標驅動放行節奏，延遲確認與自動回退&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Stripe 這個案例在講的是交易系統如何把重試、遷移與部署都設計成可回復的操作。讀者先抓 idempotency 與 zero-downtime migration 這兩個原語，再看它們怎麼保護支付流程不被重試與變更放大。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當客戶端會重送請求時，idempotency key 讓 server 能把重試視為同一筆交易。當資料結構需要調整時，零停機遷移則把高風險變更拆成可驗證的小步驟，避免一次把整個 payment path 推到不可回復的狀態。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否讓同一筆請求重送後仍得到同一個結果&lt;/li>
&lt;li>能否把 migration 拆成可觀察、可回滾的小階段&lt;/li>
&lt;li>能否區分 client retry 與 server duplicate processing&lt;/li>
&lt;li>能否把 deploy strategy 和交易一致性放在同一個判準下&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Stripe 的可靠性核心是把交易語義寫進系統邊界，這和 GitHub 的 replication、一樣都在處理「重複動作不能造成雙重結果」的問題。差別在於 Stripe 面對的是金流，容錯成本更高，所以 idempotency 與 zero-downtime migration 會比一般平台更早變成硬要求。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>idempotency key 讓同一筆請求重送後，系統仍能回到相同交易結果。&lt;/li>
&lt;li>zero-downtime migration 把高風險資料變更拆成可驗證的小階段。&lt;/li>
&lt;li>canary deploy 讓交易流量先經過小範圍驗證。&lt;/li>
&lt;li>game day 讓支付與資料遷移的失效路徑先被演練。&lt;/li>
&lt;li>retry semantics 讓 client 重送不會變成雙重扣款。&lt;/li>
&lt;li>monitoring &amp;amp; alerting 讓支付路徑的異常先在訊號層浮出來。&lt;/li>
&lt;li>operational simplicity 讓流程越少分支，越容易守住交易正確性。&lt;/li>
&lt;li>safe deploy strategy 讓變更節奏和風險控制綁在一起。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://stripe.com/blog/idempotency">Designing robust and predictable APIs with idempotency&lt;/a>：idempotency key 與重試安全的官方文章。&lt;/li>
&lt;li>&lt;a href="https://stripe.com/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations">How Stripe’s document databases supported 99.999% uptime with zero-downtime data migrations&lt;/a>：零停機資料遷移與可靠性投資的官方案例。&lt;/li>
&lt;li>&lt;a href="https://stripe.com/blog/engineering">Stripe Engineering&lt;/a>：Stripe Engineering 內容總入口，補 deploy / CI / reliability 的延伸脈絡。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Stripe 是金流場景的可靠性教學標竿、deploy strategy 與 idempotency 設計是 API platform 的工程典範。教學重點在「金流不可重複扣款 / 不可漏扣款」如何透過工程實踐保證。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Deploy strategy：canary / staged rollout 的實作節奏</li>
<li>Game Day：Stripe 公開的 game day 設計與運作</li>
<li>Idempotency Key：API 設計層面的 retry safety</li>
<li>Increasing reliability：從 99% 到 99.999% 的逐階段工程投資</li>
<li>Capture the flag：內部紅藍演練（這是 Stripe 自有的、不是套 07 的紅藍）</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Idempotency Key</td>
          <td>API 重試安全的工程實作</td>
      </tr>
      <tr>
          <td>Game Day</td>
          <td>演練設計、scope、後續 action items</td>
      </tr>
      <tr>
          <td>Canary Deploy</td>
          <td>rollout 節奏、自動 rollback 條件</td>
      </tr>
      <tr>
          <td>Database online migration</td>
          <td>高頻交易場景的 schema 變更</td>
      </tr>
      <tr>
          <td>Monitoring &amp; Alerting</td>
          <td>金流場景的訊號設計</td>
      </tr>
  </tbody>
</table>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1</a></td>
          <td>Idempotency 與零停機遷移</td>
          <td>把交易重試與資料遷移放在同一套一致性安全模型</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">S2</a></td>
          <td>Canary Deploy 與 Progressive Rollout</td>
          <td>用交易指標驅動放行節奏，延遲確認與自動回退</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Stripe 這個案例在講的是交易系統如何把重試、遷移與部署都設計成可回復的操作。讀者先抓 idempotency 與 zero-downtime migration 這兩個原語，再看它們怎麼保護支付流程不被重試與變更放大。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當客戶端會重送請求時，idempotency key 讓 server 能把重試視為同一筆交易。當資料結構需要調整時，零停機遷移則把高風險變更拆成可驗證的小步驟，避免一次把整個 payment path 推到不可回復的狀態。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否讓同一筆請求重送後仍得到同一個結果</li>
<li>能否把 migration 拆成可觀察、可回滾的小階段</li>
<li>能否區分 client retry 與 server duplicate processing</li>
<li>能否把 deploy strategy 和交易一致性放在同一個判準下</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Stripe 的可靠性核心是把交易語義寫進系統邊界，這和 GitHub 的 replication、一樣都在處理「重複動作不能造成雙重結果」的問題。差別在於 Stripe 面對的是金流，容錯成本更高，所以 idempotency 與 zero-downtime migration 會比一般平台更早變成硬要求。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>idempotency key 讓同一筆請求重送後，系統仍能回到相同交易結果。</li>
<li>zero-downtime migration 把高風險資料變更拆成可驗證的小階段。</li>
<li>canary deploy 讓交易流量先經過小範圍驗證。</li>
<li>game day 讓支付與資料遷移的失效路徑先被演練。</li>
<li>retry semantics 讓 client 重送不會變成雙重扣款。</li>
<li>monitoring &amp; alerting 讓支付路徑的異常先在訊號層浮出來。</li>
<li>operational simplicity 讓流程越少分支，越容易守住交易正確性。</li>
<li>safe deploy strategy 讓變更節奏和風險控制綁在一起。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://stripe.com/blog/idempotency">Designing robust and predictable APIs with idempotency</a>：idempotency key 與重試安全的官方文章。</li>
<li><a href="https://stripe.com/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations">How Stripe’s document databases supported 99.999% uptime with zero-downtime data migrations</a>：零停機資料遷移與可靠性投資的官方案例。</li>
<li><a href="https://stripe.com/blog/engineering">Stripe Engineering</a>：Stripe Engineering 內容總入口，補 deploy / CI / reliability 的延伸脈絡。</li>
</ul>
]]></content:encoded></item><item><title>8.4 Microsoft：雲端基礎設施的一部分</title><link>https://tarrragon.github.io/blog/go/08-case-studies/microsoft/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/microsoft/</guid><description>&lt;p>Microsoft 的官方案例文字不長，但方向很清楚：Go 被用來支撐雲端基礎設施的一部分。這類案例的重點通常在平台層、支援工具與雲端服務周邊。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/microsoft">How Microsoft Embraces Go&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很適合平台與基礎設施工具。&lt;/li>
&lt;li>雲端工程很重視部署單純性與長期可維護性。&lt;/li>
&lt;li>Go 常被放在內部治理、雲端元件與自動化流程中。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/Microsoft/cobalt">Microsoft/cobalt&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/azure/osdu-infrastructure">azure/osdu-infrastructure&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這些公開 repo 可以用來理解 Microsoft 生態裡的雲端基礎設施與自動化工作方式。即使它們不一定只講一件產品，仍很適合對照 Go 的平台語言角色。&lt;/p></description><content:encoded><![CDATA[<p>Microsoft 的官方案例文字不長，但方向很清楚：Go 被用來支撐雲端基礎設施的一部分。這類案例的重點通常在平台層、支援工具與雲端服務周邊。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/microsoft">How Microsoft Embraces Go</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很適合平台與基礎設施工具。</li>
<li>雲端工程很重視部署單純性與長期可維護性。</li>
<li>Go 常被放在內部治理、雲端元件與自動化流程中。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/Microsoft/cobalt">Microsoft/cobalt</a></li>
<li><a href="https://github.com/azure/osdu-infrastructure">azure/osdu-infrastructure</a></li>
</ul>
<p>這些公開 repo 可以用來理解 Microsoft 生態裡的雲端基礎設施與自動化工作方式。即使它們不一定只講一件產品，仍很適合對照 Go 的平台語言角色。</p>
]]></content:encoded></item><item><title>案例：LRU 快取</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/git_utils.py&lt;/code> 的 &lt;code>is_protected_branch()&lt;/code> 和 &lt;code>is_allowed_branch()&lt;/code> 函式，展示如何用 &lt;code>functools.lru_cache&lt;/code> 快取重複的分支檢查結果。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化&lt;/a>&lt;/li>
&lt;li>基本的 Git 分支概念&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>git_utils.py&lt;/code> 提供兩個分支檢查函式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">fnmatch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 保護分支列表（支援 glob 模式）&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">PROTECTED_BRANCHES&lt;/span> &lt;span class="o">=&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="s2">&amp;#34;main&amp;#34;&lt;/span>&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 class="s2">&amp;#34;master&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;develop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;release/*&amp;#34;&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="s2">&amp;#34;production&amp;#34;&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="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 允許編輯的分支模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">ALLOWED_BRANCHES&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;feat/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;feature/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;fix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;hotfix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;bugfix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;chore/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;docs/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;refactor/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;test/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查是否為保護分支
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> branch: 分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> bool: 如果是保護分支返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">PROTECTED_BRANCHES&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">fnmatch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fnmatch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查是否為允許編輯的分支
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> branch: 分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> bool: 如果是允許編輯的分支返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">ALLOWED_BRANCHES&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">fnmatch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fnmatch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：迴圈遍歷模式清單，易於理解&lt;/li>
&lt;li>&lt;strong>彈性高&lt;/strong>：支援 glob 模式，可輕易新增規則&lt;/li>
&lt;li>&lt;strong>無狀態&lt;/strong>：純函數，沒有副作用&lt;/li>
&lt;/ol>
&lt;h3 id="重複計算的開銷">重複計算的開銷&lt;/h3>
&lt;p>在 Hook 驗證流程中，同一個分支可能被檢查多次：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_config&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&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="s2">&amp;#34;&amp;#34;&amp;#34;驗證 Hook 配置&amp;#34;&amp;#34;&amp;#34;&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">errors&lt;/span> &lt;span class="o">=&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">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第一次檢查：是否允許執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Cannot run on protected branch&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 其他驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次檢查：是否需要額外確認&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">hook_config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;dangerous&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Dangerous operation on protected branch&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">errors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_multiple_hooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;處理多個 Hook&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">hooks&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 每個 Hook 都會檢查分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 再次檢查保護狀態&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 特殊處理 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>問題分析&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>同一個分支名稱被重複檢查多次&lt;/li>
&lt;li>每次檢查都要遍歷整個模式清單&lt;/li>
&lt;li>&lt;code>fnmatch.fnmatch()&lt;/code> 雖然快，但重複呼叫仍是浪費&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>測量重複呼叫的影響&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark_repeated_calls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&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="s2">&amp;#34;&amp;#34;&amp;#34;測量重複呼叫的時間&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&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="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&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">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 測量結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10000 次呼叫: ~0.05 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 每次呼叫: ~5 微秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># 看起來很快，但如果在熱路徑上...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># 100 個 Hook x 10 次檢查 = 1000 次呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># 累積起來就有影響了&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="lru_cache-基礎">lru_cache 基礎&lt;/h3>
&lt;p>&lt;code>functools.lru_cache&lt;/code> 是 Python 內建的記憶化（memoization）裝飾器：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/git_utils.py</code> 的 <code>is_protected_branch()</code> 和 <code>is_allowed_branch()</code> 函式，展示如何用 <code>functools.lru_cache</code> 快取重複的分支檢查結果。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化</a></li>
<li>基本的 Git 分支概念</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>git_utils.py</code> 提供兩個分支檢查函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 保護分支列表（支援 glob 模式）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 允許編輯的分支模式</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    檢查是否為保護分支
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：迴圈遍歷模式清單，易於理解</li>
<li><strong>彈性高</strong>：支援 glob 模式，可輕易新增規則</li>
<li><strong>無狀態</strong>：純函數，沒有副作用</li>
</ol>
<h3 id="重複計算的開銷">重複計算的開銷</h3>
<p>在 Hook 驗證流程中，同一個分支可能被檢查多次：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="n">hook_config</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證 Hook 配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 第一次檢查：是否允許執行</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;Cannot run on protected branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># ... 其他驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 第二次檢查：是否需要額外確認</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span> <span class="ow">and</span> <span class="n">hook_config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;dangerous&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;Dangerous operation on protected branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">errors</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">process_multiple_hooks</span><span class="p">(</span><span class="n">hooks</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理多個 Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">hook</span> <span class="ow">in</span> <span class="n">hooks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># 每個 Hook 都會檢查分支</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 再次檢查保護狀態</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="c1"># ... 特殊處理 ...</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">pass</span></span></span></code></pre></div><p><strong>問題分析</strong>：</p>
<ul>
<li>同一個分支名稱被重複檢查多次</li>
<li>每次檢查都要遍歷整個模式清單</li>
<li><code>fnmatch.fnmatch()</code> 雖然快，但重複呼叫仍是浪費</li>
</ul>
<p><strong>測量重複呼叫的影響</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_repeated_calls</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量重複呼叫的時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><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="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 測量結果</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 10000 次呼叫: ~0.05 秒</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 每次呼叫: ~5 微秒</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 看起來很快，但如果在熱路徑上...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 100 個 Hook x 10 次檢查 = 1000 次呼叫</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 累積起來就有影響了</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="lru_cache-基礎">lru_cache 基礎</h3>
<p><code>functools.lru_cache</code> 是 Python 內建的記憶化（memoization）裝飾器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">expensive_function</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;結果會被快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 第一次呼叫：實際計算</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result1</span> <span class="o">=</span> <span class="n">expensive_function</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 計算並快取</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 第二次呼叫：直接返回快取結果</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result2</span> <span class="o">=</span> <span class="n">expensive_function</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 從快取讀取</span></span></span></code></pre></div><p><strong>lru_cache 的適用條件</strong>：</p>
<ol>
<li><strong>純函數</strong>：相同輸入永遠產生相同輸出</li>
<li><strong>可雜湊參數</strong>：所有參數必須是 hashable（可作為 dict 的 key）</li>
<li><strong>沒有副作用</strong>：函數不應修改外部狀態</li>
<li><strong>計算成本 &gt; 快取成本</strong>：快取有記憶體開銷，要值得</li>
</ol>
<p><strong>檢查 <code>is_protected_branch</code> 是否適用</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 純函數？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">#    是 - 相同分支名稱永遠返回相同結果</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">#    （前提：PROTECTED_BRANCHES 不會在執行時改變）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 2. 可雜湊參數？</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">#    是 - str 是 hashable</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 3. 沒有副作用？</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">#    是 - 只讀取全域常數，不修改任何狀態</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 4. 計算成本值得快取？</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">#    要測量...</span></span></span></code></pre></div><h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1加入-lru_cache-裝飾器">步驟 1：加入 lru_cache 裝飾器</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    檢查是否為保護分支（帶快取）
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">        結果會被快取，快取大小為 128 個不同的分支名稱。
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        如果 PROTECTED_BRANCHES 在執行時改變，需要呼叫
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        is_protected_branch.cache_clear() 清除快取。
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支（帶快取）
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">        結果會被快取。修改 ALLOWED_BRANCHES 後需要
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        呼叫 is_allowed_branch.cache_clear()。
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><h4 id="步驟-2驗證快取行為">步驟 2：驗證快取行為</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">verify_cache_behavior</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證快取確實在運作&#34;&#34;&#34;</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">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 第一次呼叫</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result1</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;main&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">info1</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;第一次呼叫: hits=</span><span class="si">{</span><span class="n">info1</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info1</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 輸出: hits=0, misses=1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 第二次呼叫（相同參數）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">result2</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;main&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">info2</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;第二次呼叫: hits=</span><span class="si">{</span><span class="n">info2</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info2</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 輸出: hits=1, misses=1</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 不同參數</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">result3</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;feature/new&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">info3</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;不同參數: hits=</span><span class="si">{</span><span class="n">info3</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info3</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 輸出: hits=1, misses=2</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">verify_cache_behavior</span><span class="p">()</span></span></span></code></pre></div><h3 id="maxsize-的選擇">maxsize 的選擇</h3>
<p><code>maxsize</code> 決定快取可以儲存多少個不同的輸入結果：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># maxsize=None：無限制快取</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><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></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">unlimited_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># maxsize=128（預設）：快取最多 128 個結果</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># LRU = Least Recently Used，超過時淘汰最久未使用的</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">limited_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># maxsize=1：只快取最後一個結果</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 適用於「連續呼叫通常是相同參數」的情況</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">single_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span></span></span></code></pre></div><p><strong>選擇策略</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">choose_maxsize</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    選擇 maxsize 的考量因素
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 因素 1：輸入值的多樣性</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># - 分支名稱通常不多（&lt; 100 種）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># - 單一專案可能只有 10-20 個分支</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># - maxsize=128 綽綽有餘</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 因素 2：記憶體使用</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># - 每個快取項目: key + value + overhead</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># - str 的 key 大小視長度而定</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># - bool 的 value 很小</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># - 128 個項目 &lt; 10KB，可忽略</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 因素 3：呼叫模式</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># - 如果同一個分支會被檢查很多次 → 大 maxsize</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># - 如果每次都是新分支 → 快取沒意義</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="c1"># 對於分支檢查：</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># - 通常會重複檢查目前分支</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># - maxsize=32 或 64 就足夠</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># - 用 128 是保守選擇</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><p><strong>實際測量 maxsize 的影響</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_maxsize</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較不同 maxsize 的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 模擬分支名稱</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">branches</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span> <span class="s2">&#34;master&#34;</span><span class="p">,</span> <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;feature/auth&#34;</span><span class="p">,</span> <span class="s2">&#34;feature/api&#34;</span><span class="p">,</span> <span class="s2">&#34;feature/ui&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;fix/bug1&#34;</span><span class="p">,</span> <span class="s2">&#34;fix/bug2&#34;</span><span class="p">,</span> <span class="s2">&#34;fix/bug3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;release/v1.0&#34;</span><span class="p">,</span> <span class="s2">&#34;release/v2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 模擬呼叫模式：80% 是常見分支，20% 是其他</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">common_branches</span> <span class="o">=</span> <span class="n">branches</span><span class="p">[:</span><span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">simulate_calls</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">if</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mf">0.8</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">common_branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">                <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">func</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="n">func</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># 測試不同 maxsize</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">for</span> <span class="n">maxsize</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="n">maxsize</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">def</span> <span class="nf">test_func</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">info</span> <span class="o">=</span> <span class="n">simulate_calls</span><span class="p">(</span><span class="n">test_func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;maxsize=</span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">maxsize</span><span class="p">)</span><span class="si">:</span><span class="s2">&gt;4</span><span class="si">}</span><span class="s2">: &#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;hit_rate=</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%, &#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;size=</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># 輸出範例:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"># maxsize=   1: hit_rate=65.2%, size=1</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"># maxsize=   4: hit_rate=92.8%, size=4</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"># maxsize=  16: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"># maxsize=  64: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1"># maxsize= 128: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"># maxsize=None: hit_rate=99.9%, size=11</span></span></span></code></pre></div><p><strong>結論</strong>：對於分支檢查，<code>maxsize=32</code> 就足夠。用 <code>maxsize=128</code> 是安全的預設值。</p>
<h3 id="快取命中率分析">快取命中率分析</h3>
<p><code>cache_info()</code> 提供詳細的快取統計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">analyze_cache_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;分析快取效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 模擬一個 Hook 驗證流程</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_clear</span><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"># 模擬驗證 50 個 Hook，每個 Hook 檢查 2 次</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="s2">&#34;feature/new-feature&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">50</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 每個 Hook 的驗證邏輯</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 某些 Hook 會再次檢查</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 分析結果</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">protected_info</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">allowed_info</span> <span class="o">=</span> <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 快取效能分析 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">is_protected_branch:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  未命中: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">protected_info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">maxsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">is_allowed_branch:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  未命中: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">allowed_info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">maxsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"># 輸出:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"># === 快取效能分析 ===</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># is_protected_branch:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">#   命中: 99</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1">#   未命中: 1</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1">#   命中率: 99.0%</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">#   快取大小: 1/128</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"># is_allowed_branch:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1">#   命中: 99</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1">#   未命中: 1</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1">#   命中率: 99.0%</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1">#   快取大小: 1/128</span></span></span></code></pre></div><p><strong>命中率解讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>命中率</th>
          <th>意義</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&gt; 90%</td>
          <td>快取非常有效</td>
          <td>保持現狀</td>
      </tr>
      <tr>
          <td>70-90%</td>
          <td>快取有幫助</td>
          <td>可考慮增加 maxsize</td>
      </tr>
      <tr>
          <td>50-70%</td>
          <td>效果有限</td>
          <td>檢查呼叫模式</td>
      </tr>
      <tr>
          <td>&lt; 50%</td>
          <td>快取可能沒意義</td>
          <td>考慮移除快取</td>
      </tr>
  </tbody>
</table>
<h2 id="完整程式碼">完整程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">分支檢查工具 - 帶 LRU 快取
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 functools.lru_cache 優化重複的分支檢查。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># ===== 分支配置常數 =====</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="c1"># ===== 帶快取的檢查函式 =====</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    檢查是否為保護分支（帶快取）
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_protected_branch(&#34;main&#34;)
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">        True
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_protected_branch(&#34;feature/new&#34;)
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">        False
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支（帶快取）
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_allowed_branch(&#34;feat/new-feature&#34;)
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s2">        True
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_allowed_branch(&#34;random-branch&#34;)
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="s2">        False
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="c1"># ===== 快取管理工具 =====</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="k">def</span> <span class="nf">clear_branch_caches</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="s2">    清除所有分支檢查快取
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">    當 PROTECTED_BRANCHES 或 ALLOWED_BRANCHES 在執行時
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">    被修改時，需要呼叫此函式。
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">        &gt;&gt;&gt; PROTECTED_BRANCHES.append(&#34;staging&#34;)
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="s2">        &gt;&gt;&gt; clear_branch_caches()  # 清除舊的快取結果
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="k">def</span> <span class="nf">get_cache_stats</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">    獲取快取統計資訊
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        dict: 包含兩個函式的快取統計
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">        &gt;&gt;&gt; stats = get_cache_stats()
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        &gt;&gt;&gt; print(f&#34;protected hit rate: </span><span class="si">{stats[&#39;protected&#39;][&#39;hit_rate&#39;]:.1f}</span><span class="s2">%&#34;)
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">protected</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">allowed</span> <span class="o">=</span> <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">def</span> <span class="nf">calc_hit_rate</span><span class="p">(</span><span class="n">info</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">info</span><span class="o">.</span><span class="n">misses</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="k">return</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="n">total</span> <span class="o">*</span> <span class="mi">100</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="s2">&#34;protected&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="n">calc_hit_rate</span><span class="p">(</span><span class="n">protected</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">currsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="s2">&#34;maxsize&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">maxsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="s2">&#34;allowed&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="n">calc_hit_rate</span><span class="p">(</span><span class="n">allowed</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">currsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="s2">&#34;maxsize&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">maxsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="k">def</span> <span class="nf">print_cache_stats</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="s2">&#34;&#34;&#34;印出快取統計的格式化報告&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">get_cache_stats</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 分支檢查快取統計 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">stats</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;hits&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, 未命中: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;misses&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;hit_rate&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;maxsize&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="c1"># ===== 效能比較工具 =====</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_with_without_cache</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">branches</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="s2">    比較有無快取的效能差異
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="s2">        branches: 要測試的分支列表
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="s2">        iterations: 每個分支的呼叫次數
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="s2">        dict: 效能比較結果
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="c1"># 無快取版本</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="k">def</span> <span class="nf">is_protected_no_cache</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="c1"># 測試無快取版本</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="n">branches</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="n">is_protected_no_cache</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">no_cache_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="c1"># 清除快取，測試有快取版本</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="n">branches</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">            <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="n">with_cache_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="s2">&#34;no_cache_time&#34;</span><span class="p">:</span> <span class="n">no_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="s2">&#34;with_cache_time&#34;</span><span class="p">:</span> <span class="n">with_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">no_cache_time</span> <span class="o">/</span> <span class="n">with_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="s2">&#34;cache_info&#34;</span><span class="p">:</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">
</span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== LRU 快取示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="c1"># 測試分支</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="n">test_branches</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="s2">&#34;feature/auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="s2">&#34;fix/bug-123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="s2">&#34;chore/cleanup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="s2">&#34;release/v1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">
</span></span><span class="line"><span class="ln">208</span><span class="cl">    <span class="c1"># 模擬重複呼叫</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 模擬 Hook 驗證流程（100 次迭代）:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">    <span class="n">clear_branch_caches</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">
</span></span><span class="line"><span class="ln">212</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">        <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">test_branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl">    <span class="n">print_cache_stats</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">benchmark_with_without_cache</span><span class="p">(</span><span class="n">test_branches</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  無快取: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;no_cache_time&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  有快取: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;with_cache_time&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取命中率: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>無快取</th>
          <th>lru_cache</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>O(1) 查表</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>是（lru_cache 內建鎖）</td>
      </tr>
  </tbody>
</table>
<h3 id="何時不該用快取">何時不該用快取</h3>
<h4 id="情況-1函數不是純函數">情況 1：函數不是純函數</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</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="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">get_env_value</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 問題：環境變數改變後，快取還是返回舊值</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;old&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">get_env_value</span><span class="p">(</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">))</span>  <span class="c1"># &#34;old&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;new&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">get_env_value</span><span class="p">(</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">))</span>  <span class="c1"># 仍然是 &#34;old&#34;！</span></span></span></code></pre></div><h4 id="情況-2參數不可雜湊">情況 2：參數不可雜湊</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤示範：list 不是 hashable</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">process_items</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>  <span class="c1"># TypeError!</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 解法：用 tuple 代替 list</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">def</span> <span class="nf">process_items</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">items</span><span class="p">)</span></span></span></code></pre></div><h4 id="情況-3計算太簡單">情況 3：計算太簡單</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：快取開銷可能大於計算成本</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">is_empty</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># len() 和 == 操作非常快</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 快取的雜湊計算和查表可能更慢</span></span></span></code></pre></div><h4 id="情況-4輸入值極度多樣">情況 4：輸入值極度多樣</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：每次輸入都不同，快取永遠不會命中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">process_unique_id</span><span class="p">(</span><span class="n">unique_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 如果 unique_id 每次都不同...</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">unique_id</span><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"># 快取只會一直 miss，浪費記憶體</span></span></span></code></pre></div><h4 id="情況-5需要即時反映外部變化">情況 5：需要即時反映外部變化</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：配置可能動態變化</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">is_feature_enabled</span><span class="p">(</span><span class="n">feature</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 從資料庫讀取 feature flag</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="n">db</span><span class="o">.</span><span class="n">get_feature_flag</span><span class="p">(</span><span class="n">feature</span><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"># 問題：資料庫更新後，快取還是返回舊值</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 需要額外的快取失效機制</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用 lru_cache</strong>：</p>
<ul>
<li>純函數（相同輸入，相同輸出）</li>
<li>會被重複呼叫，且輸入值有限</li>
<li>計算成本明顯高於雜湊和查表</li>
<li>配置是靜態的，不會在執行時改變</li>
</ul>
<p><strong>不適合使用</strong>：</p>
<ul>
<li>函數有副作用或依賴外部狀態</li>
<li>參數不可雜湊（list、dict、set）</li>
<li>每次輸入都不同（如 UUID、時間戳）</li>
<li>計算非常簡單（如 <code>len()</code>、<code>+</code>）</li>
</ul>
<p><strong>快速決策流程</strong>：</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">函數是純函數嗎？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 否 → 不要用 lru_cache
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── 是 → 參數都是 hashable 嗎？
</span></span><span class="line"><span class="ln">4</span><span class="cl">         ├── 否 → 轉換成 hashable（如 tuple）或不用
</span></span><span class="line"><span class="ln">5</span><span class="cl">         └── 是 → 會被重複呼叫嗎？
</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">                  └── 是 → 適合用 lru_cache</span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>
<p><strong>測量快取效益</strong>：在你的專案中找一個被重複呼叫的純函數，加入 <code>lru_cache</code>，比較效能差異。</p>
</li>
<li>
<p><strong>監控快取狀態</strong>：寫一個裝飾器，在每次呼叫後印出 <code>cache_info()</code>，觀察快取行為。</p>
</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li><strong>帶 TTL 的快取</strong>：實作一個有過期時間的快取裝飾器。提示：可以結合 <code>time.time()</code> 和 <code>lru_cache</code>。</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span><span class="p">,</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">timed_lru_cache</span><span class="p">(</span><span class="n">seconds</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">maxsize</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">128</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    帶過期時間的 LRU 快取
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        seconds: 快取過期秒數
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        maxsize: 快取大小
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    用法：
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        @timed_lru_cache(seconds=60)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        def fetch_data(url):
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            ...
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 你的實作</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</span></span></span></code></pre></div><ol start="4">
<li><strong>快取命中率監控</strong>：建立一個監控系統，追蹤多個快取函式的命中率，當命中率低於閾值時發出警告。</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li><strong>動態配置的快取失效</strong>：修改 <code>is_protected_branch</code>，支援在執行時新增保護分支，並自動失效相關快取（而不是清除所有快取）。</li>
</ol>
<p>提示：考慮用 <code>typed=True</code> 選項，或自訂快取類別。</p>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇</a></em></p>
]]></content:encoded></item><item><title>案例：泛型驗證器</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 針對特定類型設計：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Single validation issue&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&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">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation result for a single hook&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">is_compliant&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__post_init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Calculate is_compliant status&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_compliant&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">issue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">issue&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">issues&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook compliance validator - specific to Path type&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate a single hook file&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook file not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... more validation logic ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Resolve path to absolute path&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_absolute&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">p&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>針對具體需求設計&lt;/strong>：專為驗證 Hook 檔案設計，邏輯清晰&lt;/li>
&lt;li>&lt;strong>型別明確&lt;/strong>：輸入是 &lt;code>str&lt;/code>，輸出是 &lt;code>ValidationResult&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要驗證其他類型時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>需要複製大量相似程式碼&lt;/strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator&lt;/li>
&lt;li>&lt;strong>驗證邏輯無法重用&lt;/strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享&lt;/li>
&lt;li>&lt;strong>型別檢查不夠通用&lt;/strong>：&lt;code>ValidationResult&lt;/code> 綁定了 &lt;code>hook_path: str&lt;/code>，無法用於其他場景&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立通用的驗證器介面&lt;/strong>：定義 &lt;code>Validator[T]&lt;/code> 協議&lt;/li>
&lt;li>&lt;strong>支援任意輸入類型&lt;/strong>：可以驗證 &lt;code>Path&lt;/code>、&lt;code>str&lt;/code>、&lt;code>dict&lt;/code>、自訂類別&lt;/li>
&lt;li>&lt;strong>保持型別安全&lt;/strong>：靜態型別檢查器能捕捉型別錯誤&lt;/li>
&lt;li>&lt;strong>支援驗證器組合&lt;/strong>：用 &lt;code>And&lt;/code>、&lt;code>Or&lt;/code>、&lt;code>Not&lt;/code> 組合基本驗證器&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議&lt;/h4>
&lt;p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># Define a type variable for the input type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># Type variable with contravariance for Protocol&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">T_contra&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_contra&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contravariant&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">]):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> Generic validation result
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> Type parameter T represents the validated value type.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="s2"> This allows type-safe access to the validated value.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">warnings&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">add_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;ValidationResult[T]&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add an error and mark as invalid&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_valid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">add_warning&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="s2">&amp;#34;ValidationResult[T]&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add a warning (does not affect validity)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warnings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T_contra&lt;/span>&lt;span class="p">]):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2"> Generic validator protocol
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class that implements validate(value: T) -&amp;gt; ValidationResult[T]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> is considered a Validator[T].
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> Using contravariant type variable because validators consume values.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> A Validator[Animal] can validate Dog (subtype), so it&amp;#39;s contravariant.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T_contra&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate the given value and return the result&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>關鍵設計決策&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 針對特定類型設計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Single validation issue&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate is_compliant status&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook compliance validator - specific to Path type&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">hook_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </span><span class="si">{</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="c1"># ... more validation logic ...</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute path&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span> <span class="o">/</span> <span class="n">p</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>針對具體需求設計</strong>：專為驗證 Hook 檔案設計，邏輯清晰</li>
<li><strong>型別明確</strong>：輸入是 <code>str</code>，輸出是 <code>ValidationResult</code></li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要驗證其他類型時：</p>
<ul>
<li><strong>需要複製大量相似程式碼</strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator</li>
<li><strong>驗證邏輯無法重用</strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享</li>
<li><strong>型別檢查不夠通用</strong>：<code>ValidationResult</code> 綁定了 <code>hook_path: str</code>，無法用於其他場景</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立通用的驗證器介面</strong>：定義 <code>Validator[T]</code> 協議</li>
<li><strong>支援任意輸入類型</strong>：可以驗證 <code>Path</code>、<code>str</code>、<code>dict</code>、自訂類別</li>
<li><strong>保持型別安全</strong>：靜態型別檢查器能捕捉型別錯誤</li>
<li><strong>支援驗證器組合</strong>：用 <code>And</code>、<code>Or</code>、<code>Not</code> 組合基本驗證器</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議</h4>
<p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># Define a type variable for the input type</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Type variable with contravariance for Protocol</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Generic validation result
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Type parameter T represents the validated value type.
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    This allows type-safe access to the validated value.
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">warnings</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">    Generic validator protocol
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    Any class that implements validate(value: T) -&gt; ValidationResult[T]
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    is considered a Validator[T].
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Using contravariant type variable because validators consume values.
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    A Validator[Animal] can validate Dog (subtype), so it&#39;s contravariant.
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value and return the result&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><p><strong>關鍵設計決策</strong>：</p>
<ul>
<li><code>T_contra</code> 使用逆變（contravariant），因為驗證器是「消費者」</li>
<li><code>ValidationResult[T]</code> 是泛型，讓結果可以攜帶原始值的型別資訊</li>
<li>Protocol 而非 ABC，支援結構型子型別（不需要顯式繼承）</li>
</ul>
<h4 id="步驟-2實作具體驗證器">步驟 2：實作具體驗證器</h4>
<p>接下來實作幾個常用的基礎驗證器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#34;&#34;&#34;</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="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">must_be_dir</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="k">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string matches a regex pattern&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Value must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="k">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a number is within a range&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is below minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is above maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="k">return</span> <span class="n">result</span></span></span></code></pre></div><h4 id="步驟-3驗證器組合andornot">步驟 3：驗證器組合（And、Or、Not）</h4>
<p>驗證器的威力來自於組合。實作三個組合器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Sequence</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">AndValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Combine multiple validators with AND logic
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    All validators must pass for the result to be valid.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="n">validators</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">class</span> <span class="nc">OrValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    Combine multiple validators with OR logic
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    At least one validator must pass for the result to be valid.
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="n">validators</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">all_errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="c1"># At least one passed, return success</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="c1"># All failed</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;None of the validators passed. Errors: </span><span class="si">{</span><span class="s1">&#39;; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="k">class</span> <span class="nc">NotValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">    Negate a validator
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">    The result is valid if the inner validator fails.
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="c1"># If inner failed, outer succeeds</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="k">return</span> <span class="n">result</span></span></span></code></pre></div><h4 id="步驟-4型別安全的建構器">步驟 4：型別安全的建構器</h4>
<p>為了讓組合更流暢，加入建構器模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatorBuilder</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Fluent builder for composing validators
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Provides a chainable API for building complex validators.
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator to the chain&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">add_if</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build the final AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</span> <span class="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build with OR logic instead of AND&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Create a type-safe validator builder
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Usage:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        validator = (
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            validator_for(str)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            .add(NotEmptyValidator())
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">            .add(PatternValidator(r&#34;^[a-z]+$&#34;))
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            .build()
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">        )
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]()</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Generic Validator System
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">A type-safe, composable validation framework using Generic and TypeVar.
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Generic</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">Sequence</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="c1"># ===== Type Variables =====</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="c1"># ===== Core Types =====</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    Generic validation result
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    Attributes:
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        value: The validated value (preserves type information)
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        is_valid: Whether validation passed
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        errors: List of error messages (cause validation failure)
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        warnings: List of warning messages (informational only)
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">warnings</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="nf">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="fm">__bool__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Allow using result in boolean context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    Generic validator protocol
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">    Any class implementing validate(value) -&gt; ValidationResult
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">    satisfies this protocol.
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="c1"># ===== Basic Validators =====</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="k">class</span> <span class="nc">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">must_be_dir</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl"><span class="k">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string matches a regex pattern&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="k">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate number is within range&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="k">class</span> <span class="nc">LengthValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string length&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="k">class</span> <span class="nc">TypeValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate value is of expected type&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">expected_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">type_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected_type</span> <span class="o">=</span> <span class="n">expected_type</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">type_name</span> <span class="o">=</span> <span class="n">type_name</span> <span class="ow">or</span> <span class="n">expected_type</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">expected_type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">type_name</span><span class="si">}</span><span class="s2">, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># ===== Composite Validators =====</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="k">class</span> <span class="nc">AndValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with AND logic (all must pass)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="k">class</span> <span class="nc">OrValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with OR logic (at least one must pass)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">all_errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">                <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No validator passed: </span><span class="si">{</span><span class="s1">&#39;; &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl"><span class="k">class</span> <span class="nc">NotValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Negate a validator (passes if inner validator fails)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">
</span></span><span class="line"><span class="ln">228</span><span class="cl">        <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">
</span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="c1"># ===== Builder =====</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">
</span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatorBuilder</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fluent builder for composing validators&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">
</span></span><span class="line"><span class="ln">241</span><span class="cl">    <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="k">def</span> <span class="nf">add_if</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">        <span class="k">return</span> <span class="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build OR-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl"><span class="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a type-safe validator builder&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]()</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl"><span class="c1"># ===== List Validator =====</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl"><span class="k">class</span> <span class="nc">ListValidator</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate each element in a list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">element_validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span> <span class="o">=</span> <span class="n">element_validator</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">
</span></span><span class="line"><span class="ln">291</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="c1"># Check list length</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">
</span></span><span class="line"><span class="ln">300</span><span class="cl">        <span class="c1"># Validate each element</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">            <span class="k">for</span> <span class="n">warning</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">warning</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="c1"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">
</span></span><span class="line"><span class="ln">312</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Generic Validator Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">
</span></span><span class="line"><span class="ln">315</span><span class="cl">    <span class="c1"># Example 1: Basic validators</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic Validators&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;hello&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;hello&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="c1"># Example 2: Path validator</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Path Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">path_validator</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /etc/hosts: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/nonexistent&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /nonexistent: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">, errors=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="c1"># Example 3: Composed validators</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">3. Composed Validators (AND)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">
</span></span><span class="line"><span class="ln">338</span><span class="cl">    <span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">        <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">        <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">,</span> <span class="s2">&#34;Must be lowercase alphanumeric&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="p">])</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="n">test_usernames</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;ab&#34;</span><span class="p">,</span> <span class="s2">&#34;valid_user&#34;</span><span class="p">,</span> <span class="s2">&#34;Invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">25</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="k">for</span> <span class="n">username</span> <span class="ow">in</span> <span class="n">test_usernames</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  &#39;</span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    - </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="c1"># Example 4: Builder pattern</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">4. Builder Pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">email_validator</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">validator_for</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">            <span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">            <span class="s2">&#34;Invalid email format&#34;</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">        <span class="o">.</span><span class="n">build</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="n">test_emails</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;test@example.com&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="k">for</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">test_emails</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">email</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  &#39;</span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">
</span></span><span class="line"><span class="ln">373</span><span class="cl">    <span class="c1"># Example 5: List validator</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">5. List Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">
</span></span><span class="line"><span class="ln">377</span><span class="cl">    <span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">378</span><span class="cl">        <span class="n">element_validator</span><span class="o">=</span><span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">LengthValidator</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="p">]),</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="n">max_length</span><span class="o">=</span><span class="mi">5</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">
</span></span><span class="line"><span class="ln">386</span><span class="cl">    <span class="n">test_tags</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">        <span class="p">[],</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">
</span></span><span class="line"><span class="ln">392</span><span class="cl">    <span class="k">for</span> <span class="n">tags</span> <span class="ow">in</span> <span class="n">test_tags</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">393</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">395</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">tags</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">396</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">397</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">398</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    - </span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="基本使用">基本使用</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Create validators</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">path_exists</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</span><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"># Validate string</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Value cannot be empty&#34;]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Validate path</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">path_exists</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True (on Unix systems)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># Using ValidationResult in boolean context</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">if</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;test&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Validation passed!&#34;</span><span class="p">)</span></span></span></code></pre></div><h4 id="組合驗證">組合驗證</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Username validator: non-empty, 3-20 chars, lowercase alphanumeric</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Test cases</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;ab&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Length 2 &lt; minimum 3&#34;]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># OR validation: accept either email or username format</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">login_validator</span> <span class="o">=</span> <span class="n">OrValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]{2,19}$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">])</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>        <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;X&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>                  <span class="c1"># False</span></span></span></code></pre></div><h4 id="使用-builder-模式">使用 Builder 模式</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Fluent API for building validators</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">password_validator</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">validator_for</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">128</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[A-Z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain uppercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[a-z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain lowercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[0-9].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain digit&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="o">.</span><span class="n">build</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;weakpass&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># [&#34;Must contain uppercase&#34;, &#34;Must contain digit&#34;]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;Strong1Password&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span></span></span></code></pre></div><h4 id="驗證列表元素">驗證列表元素</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Validate a list of tags</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">tag_validator</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">tag_validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">max_length</span><span class="o">=</span><span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">,</span> <span class="s2">&#34;generic&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;[1] Value cannot be empty&#34;]</span></span></span></code></pre></div><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>需要技巧：需正確標註 TypeVar</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>低：直覺易懂</td>
          <td>中：需理解 Generic、Protocol</td>
      </tr>
      <tr>
          <td>IDE 支援</td>
          <td>完整：型別明確</td>
          <td>需要正確標註才能獲得完整支援</td>
      </tr>
      <tr>
          <td>執行效能</td>
          <td>略佳：無泛型開銷</td>
          <td>略差：有 Protocol 檢查開銷</td>
      </tr>
      <tr>
          <td>錯誤訊息</td>
          <td>清晰：直接指出問題</td>
          <td>可能較模糊：泛型相關錯誤不易讀</td>
      </tr>
  </tbody>
</table>
<h3 id="何時選擇泛型設計">何時選擇泛型設計？</h3>
<p><strong>選擇泛型設計</strong>當：</p>
<ul>
<li>驗證邏輯會用於多種類型</li>
<li>需要組合多個驗證器</li>
<li>正在建立可重用的驗證函式庫</li>
<li>重視編譯時期的型別安全</li>
</ul>
<p><strong>選擇具體類型</strong>當：</p>
<ul>
<li>只驗證單一特定類型</li>
<li>驗證邏輯非常簡單</li>
<li>團隊對泛型不熟悉</li>
<li>效能是關鍵考量</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要驗證多種類型的函式庫</li>
<li>驗證邏輯需要組合重用</li>
<li>重視型別安全</li>
<li>API 設計需要表達「這個驗證器接受 T 類型」</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>只驗證單一類型</li>
<li>驗證邏輯很簡單（幾行 if-else 就能搞定）</li>
<li>團隊不熟悉泛型語法</li>
<li>程式碼不會被重用</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="1-實作-rangevalidatorint-和-lengthvalidatorstr">1. 實作 <code>RangeValidator[int]</code> 和 <code>LengthValidator[str]</code></h4>
<p>參考上面的 <code>RangeValidator</code> 實作，確保它可以正確驗證整數範圍。
測試案例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">age_validator</span> <span class="o">=</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h4 id="2-實作-emailvalidator">2. 實作 <code>EmailValidator</code></h4>
<p>建立一個 Email 驗證器，組合 <code>NotEmptyValidator</code> 和 <code>PatternValidator</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">email_validator</span> <span class="o">=</span> <span class="n">EmailValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="1-實作-listvalidatort-驗證列表中的每個元素">1. 實作 <code>ListValidator[T]</code> 驗證列表中的每個元素</h4>
<p>建立一個泛型列表驗證器，可以：</p>
<ul>
<li>驗證列表長度</li>
<li>對每個元素執行子驗證器</li>
<li>收集所有錯誤，標註元素索引</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">int_list_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">int_list_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># errors: [&#34;[2] Value -3 &lt; minimum 0&#34;]</span></span></span></code></pre></div><h4 id="2-實作-conditionalvalidatort">2. 實作 <code>ConditionalValidator[T]</code></h4>
<p>建立一個條件驗證器，只在條件成立時執行驗證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Only validate age if it&#39;s provided (not None)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">optional_age_validator</span> <span class="o">=</span> <span class="n">ConditionalValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">condition</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="1-實作-schemavalidator-驗證字典結構">1. 實作 <code>SchemaValidator</code> 驗證字典結構</h4>
<p>建立一個驗證器，可以驗證字典的結構和值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">user_schema</span> <span class="o">=</span> <span class="n">SchemaValidator</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">EmailValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">},</span> <span class="n">required_keys</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><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="n">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Alice&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;alice@example.com&#34;</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="k">assert</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="o">-</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># errors: [&#34;name: Value cannot be empty&#34;, &#34;age: Value -5 &lt; minimum 0&#34;, &#34;Missing required key: email&#34;]</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>dict[str, Validator[Any]]</code> 作為 schema 類型</li>
<li>處理可選欄位和必填欄位</li>
<li>考慮巢狀 schema 的支援</li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Generic">Python typing.Generic</a></li>
<li><a href="https://peps.python.org/pep-0484/">PEP 484 - Type Hints</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://mypy.readthedocs.io/en/stable/generics.html">mypy Generics</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em>
<em>返回：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組 3.5：進階設計模式</a></em></p>
]]></content:encoded></item><item><title>1.4 實戰：與同步程式碼整合</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/</guid><description>&lt;p>本章討論如何在現有專案中引入 asyncio，以及同步與異步程式碼的整合策略。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/patterns/" data-link-title="1.3 設計模式與最佳實踐" data-link-desc="學習常見的異步設計模式，避免常見陷阱">1.3 設計模式與最佳實踐&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>在異步程式中呼叫同步函式&lt;/li>
&lt;li>在同步程式中呼叫異步函式&lt;/li>
&lt;li>制定漸進式遷移策略&lt;/li>
&lt;li>與常見框架整合&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="原理層混合程式設計的挑戰">【原理層】混合程式設計的挑戰&lt;/h2>
&lt;h3 id="兩個世界的衝突">兩個世界的衝突&lt;/h3>
&lt;p>同步和異步程式碼有根本性的差異：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 同步世界&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">sync_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 異步世界&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&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="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">response&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 非阻塞等待&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題在於：&lt;/p>
&lt;ul>
&lt;li>在異步函式中呼叫同步函式會阻塞事件迴圈&lt;/li>
&lt;li>在同步函式中無法直接 &lt;code>await&lt;/code> 異步函式&lt;/li>
&lt;/ul>
&lt;h3 id="run_in_executor橋樑">run_in_executor：橋樑&lt;/h3>
&lt;p>&lt;code>run_in_executor&lt;/code> 在執行緒池中執行同步函式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">concurrent.futures&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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="n">loop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_running_loop&lt;/span>&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"># 使用預設執行緒池&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">loop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run_in_executor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sync_blocking_func&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用自訂執行緒池&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_workers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">pool&lt;/span>&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="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">loop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run_in_executor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sync_blocking_func&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層整合策略">【設計層】整合策略&lt;/h2>
&lt;h3 id="在異步程式中呼叫同步函式">在異步程式中呼叫同步函式&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">sync_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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="k">return&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_wrapper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">loop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_running_loop&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">loop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run_in_executor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sync_fetch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">url&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&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="c1"># 並發呼叫同步函式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">async_wrapper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="在同步程式中呼叫異步函式">在同步程式中呼叫異步函式&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&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="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">response&lt;/span>&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 class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">sync_main&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="c1"># 方法 1：asyncio.run()&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 方法 2：在已有事件迴圈中（例如 Jupyter）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">loop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_event_loop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">loop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run_until_complete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="漸進式遷移策略">漸進式遷移策略&lt;/h3>





&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">階段 1：識別 I/O 瓶頸
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> └─→ profiling，找出最常等待的地方
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">階段 2：引入異步版本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> └─→ 新功能用異步，舊功能保持同步
&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">階段 3：包裝同步程式碼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> └─→ 用 run_in_executor 包裝同步函式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">階段 4：逐步替換
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> └─→ 用異步函式庫替換同步函式庫
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">階段 5：完全異步（可選）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> └─→ 整個應用改為異步&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="實作層框架整合">【實作層】框架整合&lt;/h2>
&lt;h3 id="fastapi--starlette">FastAPI / Starlette&lt;/h3>
&lt;p>FastAPI 原生支援異步：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/async&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_endpoint&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;異步完成&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/sync&amp;#34;&lt;/span>&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="k">def&lt;/span> &lt;span class="nf">sync_endpoint&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># FastAPI 會自動在執行緒池中執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;同步完成&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="aiohttp-客戶端">aiohttp 客戶端&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">aiohttp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_all&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">urls&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="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&lt;/span>&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 class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">])&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="sqlalchemy-20">SQLAlchemy 2.0&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.ext.asyncio&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_async_engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AsyncSession&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sqlalchemy.orm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">sessionmaker&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_async_engine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;postgresql+asyncpg://...&amp;#34;&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="n">async_session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sessionmaker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">class_&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">AsyncSession&lt;/span>&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">async_session&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&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">select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">user_id&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="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">scalar_one_or_none&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="測試策略">【測試策略】&lt;/h2>
&lt;h3 id="測試異步程式碼">測試異步程式碼&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pytest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># pytest-asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="nd">@pytest.mark.asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_async_function&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用 asyncio.run&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_with_asyncio_run&lt;/span>&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="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">async_fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">assert&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="mock-異步函式">Mock 異步函式&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">unittest.mock&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AsyncMock&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="nd">@pytest.mark.asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_with_mock&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="n">mock_fetch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AsyncMock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">return_value&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;mocked data&amp;#34;&lt;/span>&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="k">with&lt;/span> &lt;span class="n">patch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;module.async_fetch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mock_fetch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">process_data&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="k">assert&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;processed: mocked data&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="思考題">思考題&lt;/h2>
&lt;ol>
&lt;li>為什麼 FastAPI 可以同時支援同步和異步端點？&lt;/li>
&lt;li>&lt;code>run_in_executor&lt;/code> 使用執行緒池，會不會有 GIL 的問題？&lt;/li>
&lt;li>在什麼情況下不值得遷移到 asyncio？&lt;/li>
&lt;/ol>
&lt;h2 id="實作練習">實作練習&lt;/h2>
&lt;ol>
&lt;li>將一個使用 requests 的爬蟲改寫為使用 aiohttp&lt;/li>
&lt;li>實作一個支援同步和異步呼叫的函式庫包裝器&lt;/li>
&lt;li>為異步程式碼撰寫單元測試&lt;/li>
&lt;/ol>
&lt;h2 id="延伸閱讀">延伸閱讀&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://fastapi.tiangolo.com/">FastAPI 官方文件&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html">SQLAlchemy 異步文件&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.aiohttp.org/">aiohttp 文件&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>上一章：&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/patterns/" data-link-title="1.3 設計模式與最佳實踐" data-link-desc="學習常見的異步設計模式，避免常見陷阱">設計模式與最佳實踐&lt;/a>&lt;/em>
&lt;em>下一模組：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&lt;/a>&lt;/em>&lt;/p></description><content:encoded><![CDATA[<p>本章討論如何在現有專案中引入 asyncio，以及同步與異步程式碼的整合策略。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/patterns/" data-link-title="1.3 設計模式與最佳實踐" data-link-desc="學習常見的異步設計模式，避免常見陷阱">1.3 設計模式與最佳實踐</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>在異步程式中呼叫同步函式</li>
<li>在同步程式中呼叫異步函式</li>
<li>制定漸進式遷移策略</li>
<li>與常見框架整合</li>
</ol>
<hr>
<h2 id="原理層混合程式設計的挑戰">【原理層】混合程式設計的挑戰</h2>
<h3 id="兩個世界的衝突">兩個世界的衝突</h3>
<p>同步和異步程式碼有根本性的差異：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 同步世界</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">sync_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>  <span class="c1"># 阻塞等待</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 異步世界</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">()</span>  <span class="c1"># 非阻塞等待</span></span></span></code></pre></div><p>問題在於：</p>
<ul>
<li>在異步函式中呼叫同步函式會阻塞事件迴圈</li>
<li>在同步函式中無法直接 <code>await</code> 異步函式</li>
</ul>
<h3 id="run_in_executor橋樑">run_in_executor：橋樑</h3>
<p><code>run_in_executor</code> 在執行緒池中執行同步函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><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"># 使用預設執行緒池</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">sync_blocking_func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 使用自訂執行緒池</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">pool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">sync_blocking_func</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="設計層整合策略">【設計層】整合策略</h2>
<h3 id="在異步程式中呼叫同步函式">在異步程式中呼叫同步函式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">sync_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">.</span><span class="n">text</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="k">async</span> <span class="k">def</span> <span class="nf">async_wrapper</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">sync_fetch</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 並發呼叫同步函式</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">]</span> <span class="o">*</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">async_wrapper</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span></span></span></code></pre></div><h3 id="在同步程式中呼叫異步函式">在同步程式中呼叫異步函式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">sync_main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 方法 1：asyncio.run()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 方法 2：在已有事件迴圈中（例如 Jupyter）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">))</span></span></span></code></pre></div><h3 id="漸進式遷移策略">漸進式遷移策略</h3>





<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">階段 1：識別 I/O 瓶頸
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    └─→ profiling，找出最常等待的地方
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">階段 2：引入異步版本
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    └─→ 新功能用異步，舊功能保持同步
</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">階段 3：包裝同步程式碼
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    └─→ 用 run_in_executor 包裝同步函式
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">階段 4：逐步替換
</span></span><span class="line"><span class="ln">11</span><span class="cl">    └─→ 用異步函式庫替換同步函式庫
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">階段 5：完全異步（可選）
</span></span><span class="line"><span class="ln">14</span><span class="cl">    └─→ 整個應用改為異步</span></span></code></pre></div><hr>
<h2 id="實作層框架整合">【實作層】框架整合</h2>
<h3 id="fastapi--starlette">FastAPI / Starlette</h3>
<p>FastAPI 原生支援異步：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/async&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_endpoint</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;異步完成&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/sync&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">sync_endpoint</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># FastAPI 會自動在執行緒池中執行</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;同步完成&#34;</span><span class="p">}</span></span></span></code></pre></div><h3 id="aiohttp-客戶端">aiohttp 客戶端</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">aiohttp</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">fetch_all</span><span class="p">(</span><span class="n">urls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">])</span></span></span></code></pre></div><h3 id="sqlalchemy-20">SQLAlchemy 2.0</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.ext.asyncio</span> <span class="kn">import</span> <span class="n">create_async_engine</span><span class="p">,</span> <span class="n">AsyncSession</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_async_engine</span><span class="p">(</span><span class="s2">&#34;postgresql+asyncpg://...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">async_session</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="n">class_</span><span class="o">=</span><span class="n">AsyncSession</span><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="k">async</span> <span class="k">def</span> <span class="nf">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">async_session</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">select</span><span class="p">(</span><span class="n">User</span><span class="p">)</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">id</span> <span class="o">==</span> <span class="n">user_id</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="k">return</span> <span class="n">result</span><span class="o">.</span><span class="n">scalar_one_or_none</span><span class="p">()</span></span></span></code></pre></div><hr>
<h2 id="測試策略">【測試策略】</h2>
<h3 id="測試異步程式碼">測試異步程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># pytest-asyncio</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_async_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">assert</span> <span class="n">result</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 使用 asyncio.run</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">test_with_asyncio_run</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">assert</span> <span class="n">result</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="mock-異步函式">Mock 異步函式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">AsyncMock</span><span class="p">,</span> <span class="n">patch</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_with_mock</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">mock_fetch</span> <span class="o">=</span> <span class="n">AsyncMock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="s2">&#34;mocked data&#34;</span><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="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;module.async_fetch&#34;</span><span class="p">,</span> <span class="n">mock_fetch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">process_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="s2">&#34;processed: mocked data&#34;</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 FastAPI 可以同時支援同步和異步端點？</li>
<li><code>run_in_executor</code> 使用執行緒池，會不會有 GIL 的問題？</li>
<li>在什麼情況下不值得遷移到 asyncio？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>將一個使用 requests 的爬蟲改寫為使用 aiohttp</li>
<li>實作一個支援同步和異步呼叫的函式庫包裝器</li>
<li>為異步程式碼撰寫單元測試</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://fastapi.tiangolo.com/">FastAPI 官方文件</a></li>
<li><a href="https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html">SQLAlchemy 異步文件</a></li>
<li><a href="https://docs.aiohttp.org/">aiohttp 文件</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/patterns/" data-link-title="1.3 設計模式與最佳實踐" data-link-desc="學習常見的異步設計模式，避免常見陷阱">設計模式與最佳實踐</a></em>
<em>下一模組：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></em></p>
]]></content:encoded></item><item><title>9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/</guid><description>&lt;p>這個案例的核心責任是提供「key-value 持續高吞吐」的極限參考點。廣告事件量測屬 &lt;em>write-heavy + read-heavy 同時存在&lt;/em> 的負載 — 每個曝光都要寫進度、每個曝光也都要查 metadata。這類負載沒有明顯峰谷、是長期 sustained growth、跟事件型峰值的容量設計邏輯不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Amazon Ads 在 DynamoDB 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers&lt;/a>）：&lt;/p>
&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>9000 萬 reads / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫吞吐&lt;/td>
 &lt;td>500 萬 writes / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>99.999%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用途&lt;/td>
 &lt;td>廣告事件量測&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>讀寫比約 18:1。這個比例反映「曝光發生 1 次、後續查詢可能發生 18 次」的廣告計費邏輯。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最重要的不是「DynamoDB 能撐多少」、而是「為什麼可以這樣設計」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單表分散到上千個 partition&lt;/strong>：DynamoDB 把每個 table 拆成多個 partition、每個 partition 內部還可以再分散。9000 萬 reads / 秒 是上千個 partition 加總的結果、單一節點達不到這個量級。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的 sharding 邊界、跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 partition 設計。&lt;/li>
&lt;li>&lt;strong>partition key 選擇直接決定容量上限&lt;/strong>：DynamoDB 的容量是「每 partition 上限 × partition 數量」。partition key 不均勻會出現 hot partition、實際容量遠低於名義容量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 saturation 不一定是整體 saturation、而是 &lt;em>最熱的 partition&lt;/em> saturation。&lt;/li>
&lt;li>&lt;strong>99.999% availability ≈ 5 分鐘 / 年的容錯&lt;/strong>：廣告計費 1 分鐘斷線可能損失幾百萬美金廣告收入。這個 SLO 不是行銷數字、是真實的營收邊界。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「9000 萬 reads / 秒」這種敘述通常是 &lt;em>年度峰值的最高一秒&lt;/em>、不是平均值。容量規劃要區分「最大瞬時」、「99 百分位平均」、「常態流量」三個不同口徑。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>partition key 設計是 KV 容量的第一決策&lt;/strong>：均勻分散、避免 hot partition、必要時加 random suffix 強制分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema design 章節。&lt;/li>
&lt;li>&lt;strong>read-heavy 跟 write-heavy 比例變化是容量警訊&lt;/strong>：當業務邏輯改變（例如新增即時報表）、讀寫比可能跳一個量級、原本的容量規劃會失效。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 持續監控比例變化。&lt;/li>
&lt;li>&lt;strong>on-demand vs provisioned 是成本 vs 反應速度的取捨&lt;/strong>：on-demand 自動擴容但成本高、provisioned 便宜但需要預測。Amazon Ads 這種 sustained workload 通常用 provisioned + auto scaling、不用 on-demand。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Cloud Bigtable + 良好 row key 設計、Azure Cosmos DB partition key 設計都是對等概念。差異是 DynamoDB 的 partition 透明度（你看不到 partition 數量）vs Bigtable 的明確 tablet 模型。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「key-value 持續高吞吐」的極限參考點。廣告事件量測屬 <em>write-heavy + read-heavy 同時存在</em> 的負載 — 每個曝光都要寫進度、每個曝光也都要查 metadata。這類負載沒有明顯峰谷、是長期 sustained growth、跟事件型峰值的容量設計邏輯不同。</p>
<h2 id="觀察">觀察</h2>
<p>Amazon Ads 在 DynamoDB 的關鍵數字（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>讀吞吐</td>
          <td>9000 萬 reads / 秒</td>
      </tr>
      <tr>
          <td>寫吞吐</td>
          <td>500 萬 writes / 秒</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>99.999%</td>
      </tr>
      <tr>
          <td>用途</td>
          <td>廣告事件量測</td>
      </tr>
  </tbody>
</table>
<p>讀寫比約 18:1。這個比例反映「曝光發生 1 次、後續查詢可能發生 18 次」的廣告計費邏輯。</p>
<h2 id="判讀">判讀</h2>
<p>這個案例最重要的不是「DynamoDB 能撐多少」、而是「為什麼可以這樣設計」。</p>
<ol>
<li><strong>單表分散到上千個 partition</strong>：DynamoDB 把每個 table 拆成多個 partition、每個 partition 內部還可以再分散。9000 萬 reads / 秒 是上千個 partition 加總的結果、單一節點達不到這個量級。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的 sharding 邊界、跟 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 partition 設計。</li>
<li><strong>partition key 選擇直接決定容量上限</strong>：DynamoDB 的容量是「每 partition 上限 × partition 數量」。partition key 不均勻會出現 hot partition、實際容量遠低於名義容量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 saturation 不一定是整體 saturation、而是 <em>最熱的 partition</em> saturation。</li>
<li><strong>99.999% availability ≈ 5 分鐘 / 年的容錯</strong>：廣告計費 1 分鐘斷線可能損失幾百萬美金廣告收入。這個 SLO 不是行銷數字、是真實的營收邊界。對應 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>。</li>
</ol>
<p>需要警惕：「9000 萬 reads / 秒」這種敘述通常是 <em>年度峰值的最高一秒</em>、不是平均值。容量規劃要區分「最大瞬時」、「99 百分位平均」、「常態流量」三個不同口徑。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>partition key 設計是 KV 容量的第一決策</strong>：均勻分散、避免 hot partition、必要時加 random suffix 強制分散。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design 章節。</li>
<li><strong>read-heavy 跟 write-heavy 比例變化是容量警訊</strong>：當業務邏輯改變（例如新增即時報表）、讀寫比可能跳一個量級、原本的容量規劃會失效。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 持續監控比例變化。</li>
<li><strong>on-demand vs provisioned 是成本 vs 反應速度的取捨</strong>：on-demand 自動擴容但成本高、provisioned 便宜但需要預測。Amazon Ads 這種 sustained workload 通常用 provisioned + auto scaling、不用 on-demand。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>跨平台等效：GCP Cloud Bigtable + 良好 row key 設計、Azure Cosmos DB partition key 設計都是對等概念。差異是 DynamoDB 的 partition 透明度（你看不到 partition 數量）vs Bigtable 的明確 tablet 模型。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 KV 高吞吐架構 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想避免 hot partition → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>想對照其他 KV 案例 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth Cosmos DB</a>（Azure 全球分散）</li>
<li>想深入 DynamoDB hot partition 反模式 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想拆 access pattern 對應的 single-table design → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a></li>
<li>想評估 on-demand vs provisioned 切換時機 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/handle-traffic-spikes-with-amazon-dynamodb-provisioned-capacity/">Handle traffic spikes with Amazon DynamoDB provisioned capacity</a></li>
<li><a href="https://aws.amazon.com/blogs/database/demystifying-amazon-dynamodb-on-demand-capacity-mode/">Demystifying Amazon DynamoDB on-demand capacity mode</a></li>
</ul>
]]></content:encoded></item><item><title>2.C5 Shopify：Write-through Cache 在高讀流量的實作</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-write-through-cache-at-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-write-through-cache-at-scale/</guid><description>&lt;p>這個案例的核心責任是把快取從被動補貨模式，轉成資料寫入時即同步更新的模式。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Shopify 在高讀取路徑以 write-through 策略降低 miss 風險，改善熱門資料讀取穩定性。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 cache miss 成本過高且資料更新可控時，write-through 能降低讀路徑抖動。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把寫入流程與快取更新綁定。&lt;/li>
&lt;li>對失敗寫入設計補償與重試。&lt;/li>
&lt;li>用 hit rate 與 stale rate 檢驗策略收益。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://shopify.engineering/horizontally-scaling-the-rails-backend-of-shop-app-with-vitess">How Shop App uses write-through caching&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把快取從被動補貨模式，轉成資料寫入時即同步更新的模式。</p>
<h2 id="觀察">觀察</h2>
<p>Shopify 在高讀取路徑以 write-through 策略降低 miss 風險，改善熱門資料讀取穩定性。</p>
<h2 id="判讀">判讀</h2>
<p>當 cache miss 成本過高且資料更新可控時，write-through 能降低讀路徑抖動。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把寫入流程與快取更新綁定。</li>
<li>對失敗寫入設計補償與重試。</li>
<li>用 hit rate 與 stale rate 檢驗策略收益。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2 cache aside</a> 與 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/horizontally-scaling-the-rails-backend-of-shop-app-with-vitess">How Shop App uses write-through caching</a></li>
</ul>
]]></content:encoded></item><item><title>3.C5 Slack：Job Queue 演進到 Kafka + Redis</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/</guid><description>&lt;p>這個案例的核心責任是說明工作佇列轉換常是拓樸重整，而不是單點替換。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Slack 在 job queue 擴展中使用 Kafka 與 Redis 分工，處理吞吐與即時性需求。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當背景工作同時要高吞吐與快速反應，單一通道模型通常會變成瓶頸。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把不同工作類型切到不同傳遞路徑。&lt;/li>
&lt;li>分別治理持久性與即時性目標。&lt;/li>
&lt;li>以 lag、重試與失敗重播驗證穩定性。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://slack.engineering/scaling-slacks-job-queue/">Scaling Slack&amp;rsquo;s Job Queue&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明工作佇列轉換常是拓樸重整，而不是單點替換。</p>
<h2 id="觀察">觀察</h2>
<p>Slack 在 job queue 擴展中使用 Kafka 與 Redis 分工，處理吞吐與即時性需求。</p>
<h2 id="判讀">判讀</h2>
<p>當背景工作同時要高吞吐與快速反應，單一通道模型通常會變成瓶頸。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把不同工作類型切到不同傳遞路徑。</li>
<li>分別治理持久性與即時性目標。</li>
<li>以 lag、重試與失敗重播驗證穩定性。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a> 與 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://slack.engineering/scaling-slacks-job-queue/">Scaling Slack&rsquo;s Job Queue</a></li>
</ul>
]]></content:encoded></item><item><title>4.C5 Google Cloud：Cloud Trace 導入 OTLP 入口</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/</guid><description>&lt;p>這個案例的核心責任是說明 observability 平台轉換常來自資料通道標準化需求。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Google Cloud 在 Cloud Trace 提供 OTLP 支援，降低應用程式對特定傳輸介面的綁定。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當團隊要跨多環境與多工具，標準化傳輸協定能減少重複 instrumentation 與遷移摩擦。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>將 collector 與 in-process exporter 對齊 OTLP。&lt;/li>
&lt;li>把 trace schema 與 sampling 規則集中治理。&lt;/li>
&lt;li>在遷移期保留舊通道與新通道比對。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/management-tools/opentelemetry-now-in-google-cloud-observability">OTLP in Google Cloud Observability&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 observability 平台轉換常來自資料通道標準化需求。</p>
<h2 id="觀察">觀察</h2>
<p>Google Cloud 在 Cloud Trace 提供 OTLP 支援，降低應用程式對特定傳輸介面的綁定。</p>
<h2 id="判讀">判讀</h2>
<p>當團隊要跨多環境與多工具，標準化傳輸協定能減少重複 instrumentation 與遷移摩擦。</p>
<h2 id="策略">策略</h2>
<ol>
<li>將 collector 與 in-process exporter 對齊 OTLP。</li>
<li>把 trace schema 與 sampling 規則集中治理。</li>
<li>在遷移期保留舊通道與新通道比對。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/management-tools/opentelemetry-now-in-google-cloud-observability">OTLP in Google Cloud Observability</a></li>
</ul>
]]></content:encoded></item><item><title>5.C5 Miro：Managed EKS 遷移</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/miro-managed-eks-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/miro-managed-eks-migration/</guid><description>&lt;p>這個案例的核心責任是說明平台遷移也會改變團隊職責分工。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Miro 從自維運 Kubernetes 遷移到 managed EKS。遷移前的狀態是平台團隊大部分精力花在叢集本身的運維——control plane 升級、node AMI 維護、etcd 備份、安全修補。這些工作是必要的，但它們跟「讓開發者更快交付功能」沒有直接關聯。&lt;/p>
&lt;p>遷移後 managed EKS 接管了 control plane 運維。平台團隊的工作重心從「維持叢集跑起來」轉向「定義 release flow、observability convention、developer experience」。這個轉變是 managed 平台的組織層面價值，技術層面的價值（省維運、自動升級）反而是次要的。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>平台託管化的價值在讓團隊把心力從底層維護轉到交付效率與可靠性策略。這個判讀成立的前提是組織主動重新定義職責邊界——managed 平台不會自動帶來組織轉型，它只是移除了一類維運負擔。如果平台團隊在遷移後沒有重新定義職責，很容易繼續用舊模式工作（只是工作量少了），錯失把省下的精力轉到更高價值工作的機會。&lt;/p>
&lt;p>另一個判讀是 managed 平台引入新的 grey zone。control plane 由供應商管理，但 cluster-internal 元件（CNI、ingress controller、service mesh、cluster DNS）的 ownership 需要顯式界定。Miro 的經驗顯示這些 grey zone 若不在 day-1 處理，後續會在事故時暴露——「以為供應商在管」跟「供應商認為客戶在管」的認知差距，會讓故障排查繞圈。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>先定義遷移後的平台責任邊界&lt;/strong>：列出四層責任矩陣——cluster 層（供應商管）、cluster-internal 層（platform team 管）、application 層（service team 管）、跨層議題（協作）。每層有明確 owner，避免 grey zone。責任矩陣的詳細結構見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 Managed 平台跟團隊職責邊界&lt;/a>。&lt;/li>
&lt;li>&lt;strong>以自動化流程取代手動平台操作&lt;/strong>：遷移前的手動操作（node 升級、cert rotation、backup restore）在 managed 平台上由供應商或 IaC 接管。剩餘的手動操作（namespace provisioning、resource quota 設定、network policy review）也要自動化或流程化，避免依賴個人經驗。&lt;/li>
&lt;li>&lt;strong>將 incident 與 release policy 接回平台治理&lt;/strong>：managed 平台的 incident 跟 self-managed 不同——control plane 故障由供應商處理，但供應商的 incident 訊號要進入自家的 incident timeline。release policy（升級節奏、canary 比例、rollback 條件）在 managed 平台上仍是 platform team 的責任。&lt;/li>
&lt;/ol>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>從 managed 回退到 self-managed 的成本極高（要重建 control plane 運維能力），因此這類遷移的回退策略通常是「在 managed 平台內回退」而非「回到 self-managed」。具體做法是保留舊叢集一段時間作為 fallback，但同時接受「回到 self-managed 不是選項」的設計假設。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 container runtime&lt;/a> 看遷移後 runtime 層的變化驗證。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 managed 平台與職責邊界&lt;/a> 看職責矩陣的完整結構。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/attacker-view-platform-entry-risks/" data-link-title="5.5 平台與入口威脅建模（Threat Modeling）" data-link-desc="以概念層判讀部署平台弱點，聚焦入口、生命週期、設定與交付節奏">5.5 平台與入口威脅建模&lt;/a> 看遷移期攻擊面變動。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/miro-amazon-eks/">Miro on AWS containers and EKS&lt;/a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明平台遷移也會改變團隊職責分工。</p>
<h2 id="觀察">觀察</h2>
<p>Miro 從自維運 Kubernetes 遷移到 managed EKS。遷移前的狀態是平台團隊大部分精力花在叢集本身的運維——control plane 升級、node AMI 維護、etcd 備份、安全修補。這些工作是必要的，但它們跟「讓開發者更快交付功能」沒有直接關聯。</p>
<p>遷移後 managed EKS 接管了 control plane 運維。平台團隊的工作重心從「維持叢集跑起來」轉向「定義 release flow、observability convention、developer experience」。這個轉變是 managed 平台的組織層面價值，技術層面的價值（省維運、自動升級）反而是次要的。</p>
<h2 id="判讀">判讀</h2>
<p>平台託管化的價值在讓團隊把心力從底層維護轉到交付效率與可靠性策略。這個判讀成立的前提是組織主動重新定義職責邊界——managed 平台不會自動帶來組織轉型，它只是移除了一類維運負擔。如果平台團隊在遷移後沒有重新定義職責，很容易繼續用舊模式工作（只是工作量少了），錯失把省下的精力轉到更高價值工作的機會。</p>
<p>另一個判讀是 managed 平台引入新的 grey zone。control plane 由供應商管理，但 cluster-internal 元件（CNI、ingress controller、service mesh、cluster DNS）的 ownership 需要顯式界定。Miro 的經驗顯示這些 grey zone 若不在 day-1 處理，後續會在事故時暴露——「以為供應商在管」跟「供應商認為客戶在管」的認知差距，會讓故障排查繞圈。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>先定義遷移後的平台責任邊界</strong>：列出四層責任矩陣——cluster 層（供應商管）、cluster-internal 層（platform team 管）、application 層（service team 管）、跨層議題（協作）。每層有明確 owner，避免 grey zone。責任矩陣的詳細結構見 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 Managed 平台跟團隊職責邊界</a>。</li>
<li><strong>以自動化流程取代手動平台操作</strong>：遷移前的手動操作（node 升級、cert rotation、backup restore）在 managed 平台上由供應商或 IaC 接管。剩餘的手動操作（namespace provisioning、resource quota 設定、network policy review）也要自動化或流程化，避免依賴個人經驗。</li>
<li><strong>將 incident 與 release policy 接回平台治理</strong>：managed 平台的 incident 跟 self-managed 不同——control plane 故障由供應商處理，但供應商的 incident 訊號要進入自家的 incident timeline。release policy（升級節奏、canary 比例、rollback 條件）在 managed 平台上仍是 platform team 的責任。</li>
</ol>
<h2 id="回退判讀">回退判讀</h2>
<p>從 managed 回退到 self-managed 的成本極高（要重建 control plane 運維能力），因此這類遷移的回退策略通常是「在 managed 平台內回退」而非「回到 self-managed」。具體做法是保留舊叢集一段時間作為 fallback，但同時接受「回到 self-managed 不是選項」的設計假設。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/container-runtime/" data-link-title="5.1 container 與 runtime" data-link-desc="整理 image、resource limit 與啟動行為">5.1 container runtime</a> 看遷移後 runtime 層的變化驗證。回 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#managed-%e5%b9%b3%e5%8f%b0%e8%b7%9f%e5%9c%98%e9%9a%8a%e8%81%b7%e8%b2%ac%e9%82%8a%e7%95%8c" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 managed 平台與職責邊界</a> 看職責矩陣的完整結構。回 <a href="/blog/backend/05-deployment-platform/attacker-view-platform-entry-risks/" data-link-title="5.5 平台與入口威脅建模（Threat Modeling）" data-link-desc="以概念層判讀部署平台弱點，聚焦入口、生命週期、設定與交付節奏">5.5 平台與入口威脅建模</a> 看遷移期攻擊面變動。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/miro-amazon-eks/">Miro on AWS containers and EKS</a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）</li>
</ul>
]]></content:encoded></item><item><title>7.C5 Okta：2023 Support System 事件</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-support-system-incident-2023/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-support-system-incident-2023/</guid><description>&lt;p>這個案例的核心責任是提醒控制面不只在正式生產系統，也在支援工具鏈。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Okta 2023 事件顯示支援系統若涉及高權限資料與工作流程，會成為跨租戶風險放大點。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>身份與授權治理若只覆蓋產品面，忽略支援流程，仍會留下高影響面缺口。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把 support tooling 納入同等級身份治理。&lt;/li>
&lt;li>補強 session、token 與操作留痕控制。&lt;/li>
&lt;li>將異常支援活動接入告警與 incident 路由。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 identity/access boundary&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13 detection coverage&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sec.okta.com/harfiles">Okta support system case update&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是提醒控制面不只在正式生產系統，也在支援工具鏈。</p>
<h2 id="觀察">觀察</h2>
<p>Okta 2023 事件顯示支援系統若涉及高權限資料與工作流程，會成為跨租戶風險放大點。</p>
<h2 id="判讀">判讀</h2>
<p>身份與授權治理若只覆蓋產品面，忽略支援流程，仍會留下高影響面缺口。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把 support tooling 納入同等級身份治理。</li>
<li>補強 session、token 與操作留痕控制。</li>
<li>將異常支援活動接入告警與 incident 路由。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 identity/access boundary</a> 與 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13 detection coverage</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sec.okta.com/harfiles">Okta support system case update</a></li>
</ul>
]]></content:encoded></item><item><title>Atlassian</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/</guid><description>&lt;p>Atlassian 2022 的 14 天事故是多租戶誤刪 + 跨團隊協作的教學標竿。事故 post-mortem 公開度極高、揭露 IR 內部運作細節（incident commander 輪值、跨團隊溝通、客戶補償政策），是少數能完整看到大型事故 IR 流程的公開素材。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>多租戶資料模型：跨產品 tenant ID 的 cascading delete 風險&lt;/li>
&lt;li>Recovery 順序：885 個 tenants 為何不能平行恢復、需要排序&lt;/li>
&lt;li>跨團隊協作：incident commander 輪值、24x7 支援、客戶溝通分軌&lt;/li>
&lt;li>Stakeholder 通訊：customer impact 量化、補償政策、合約衝擊&lt;/li>
&lt;li>Postmortem 文化：Atlassian Incident Management Handbook 公開內容&lt;/li>
&lt;/ul>
&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>2022&lt;/td>
 &lt;td>14 天多租戶誤刪&lt;/td>
 &lt;td>大規模 IR 協作、長尾 recovery、客戶溝通&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2023&lt;/td>
 &lt;td>較小規模事故&lt;/td>
 &lt;td>對比 14 天事故的 IR 流程演化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Atlassian 這個案例在講的是多租戶 SaaS 在發生誤刪後，復原與對外通訊如何一起構成事故本體。讀者先看懂 PIR、status update 與 restore path 的責任，再把 2022 事件當成跨團隊協作與復原節奏的範例。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當事故牽涉到客戶資料或多個內部系統時，復原速度取決於能否把依賴關係一層一層還原。當事故持續時間拉長時，對外更新的節奏也要固定，讓客戶能知道哪些功能先恢復、哪些風險仍在。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否把誤刪後的復原步驟寫成明確順序&lt;/li>
&lt;li>能否把 status update 與內部復原節奏對齊&lt;/li>
&lt;li>能否說明哪些服務先恢復、哪些依賴後恢復&lt;/li>
&lt;li>能否在 PIR 中把流程缺口轉成可追蹤的改善項&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Atlassian 和 Microsoft 365 都在講企業 SaaS 的客戶通訊問題，但 Atlassian 更像是把復原流程完整攤在桌上。它也適合和 GitHub 一起看，因為兩者都能說明長時間事故裡，時間線、責任與客戶影響如何一起被管理。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2022 年 14 天 outage 代表多租戶誤刪後的長尾復原。&lt;/li>
&lt;li>PIR 與對外 update 的節奏，讓客戶能知道哪些服務先回來。&lt;/li>
&lt;li>incident commander 輪值與跨團隊協作是這類事故的核心樣本。&lt;/li>
&lt;li>補償政策與客戶溝通會直接影響事故收斂速度。&lt;/li>
&lt;li>885 個 tenants 的排序恢復讓復原順序本身成為事故管理的一部分。&lt;/li>
&lt;li>customer impact quantification 讓補償與優先恢復有可執行依據。&lt;/li>
&lt;li>multi-tenant data model 讓單一誤刪能直接跨產品擴散。&lt;/li>
&lt;li>stakeholder communication 會和技術復原一起構成事故處理流程。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/post-incident-review-april-2022-outage">Post-Incident Review on the Atlassian April 2022 outage&lt;/a>：Atlassian 2022 年大規模誤刪事件的完整 PIR。&lt;/li>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/april-2022-outage-update">Update on the Atlassian outage affecting some customers&lt;/a>：對外更新版本，適合對照復原節奏。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Atlassian 2022 的 14 天事故是多租戶誤刪 + 跨團隊協作的教學標竿。事故 post-mortem 公開度極高、揭露 IR 內部運作細節（incident commander 輪值、跨團隊溝通、客戶補償政策），是少數能完整看到大型事故 IR 流程的公開素材。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>多租戶資料模型：跨產品 tenant ID 的 cascading delete 風險</li>
<li>Recovery 順序：885 個 tenants 為何不能平行恢復、需要排序</li>
<li>跨團隊協作：incident commander 輪值、24x7 支援、客戶溝通分軌</li>
<li>Stakeholder 通訊：customer impact 量化、補償政策、合約衝擊</li>
<li>Postmortem 文化：Atlassian Incident Management Handbook 公開內容</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2022</td>
          <td>14 天多租戶誤刪</td>
          <td>大規模 IR 協作、長尾 recovery、客戶溝通</td>
      </tr>
      <tr>
          <td>2023</td>
          <td>較小規模事故</td>
          <td>對比 14 天事故的 IR 流程演化</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>Atlassian 這個案例在講的是多租戶 SaaS 在發生誤刪後，復原與對外通訊如何一起構成事故本體。讀者先看懂 PIR、status update 與 restore path 的責任，再把 2022 事件當成跨團隊協作與復原節奏的範例。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當事故牽涉到客戶資料或多個內部系統時，復原速度取決於能否把依賴關係一層一層還原。當事故持續時間拉長時，對外更新的節奏也要固定，讓客戶能知道哪些功能先恢復、哪些風險仍在。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否把誤刪後的復原步驟寫成明確順序</li>
<li>能否把 status update 與內部復原節奏對齊</li>
<li>能否說明哪些服務先恢復、哪些依賴後恢復</li>
<li>能否在 PIR 中把流程缺口轉成可追蹤的改善項</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Atlassian 和 Microsoft 365 都在講企業 SaaS 的客戶通訊問題，但 Atlassian 更像是把復原流程完整攤在桌上。它也適合和 GitHub 一起看，因為兩者都能說明長時間事故裡，時間線、責任與客戶影響如何一起被管理。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2022 年 14 天 outage 代表多租戶誤刪後的長尾復原。</li>
<li>PIR 與對外 update 的節奏，讓客戶能知道哪些服務先回來。</li>
<li>incident commander 輪值與跨團隊協作是這類事故的核心樣本。</li>
<li>補償政策與客戶溝通會直接影響事故收斂速度。</li>
<li>885 個 tenants 的排序恢復讓復原順序本身成為事故管理的一部分。</li>
<li>customer impact quantification 讓補償與優先恢復有可執行依據。</li>
<li>multi-tenant data model 讓單一誤刪能直接跨產品擴散。</li>
<li>stakeholder communication 會和技術復原一起構成事故處理流程。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/post-incident-review-april-2022-outage">Post-Incident Review on the Atlassian April 2022 outage</a>：Atlassian 2022 年大規模誤刪事件的完整 PIR。</li>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/april-2022-outage-update">Update on the Atlassian outage affecting some customers</a>：對外更新版本，適合對照復原節奏。</li>
</ul>
]]></content:encoded></item><item><title>Shopify</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/</guid><description>&lt;p>Shopify 是 BFCM（Black Friday / Cyber Monday）流量峰值的可靠性教學標竿、pod-based architecture 是 multi-tenant SaaS 的隔離典範。教學重點在「年度可預期峰值如何透過架構與演練準備」。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Pod-based Architecture：多租戶切分、商家隔離設計&lt;/li>
&lt;li>BFCM 準備：年度峰值的 capacity planning 流程&lt;/li>
&lt;li>Resiliency Matrix：列舉服務與失效模式的對照表&lt;/li>
&lt;li>Toxiproxy / Resiliency tooling：Shopify 開源的 chaos 工具&lt;/li>
&lt;li>Database sharding：MySQL 分片策略與 online resharding&lt;/li>
&lt;/ul>
&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>BFCM Capacity Planning&lt;/td>
 &lt;td>容量預測、load test 設計、實際峰值對照&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod Architecture&lt;/td>
 &lt;td>多租戶切分、failure isolation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resiliency Matrix&lt;/td>
 &lt;td>失效模式對照表的維護方法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Toxiproxy&lt;/td>
 &lt;td>TCP-level 故障注入的工程實作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database resharding&lt;/td>
 &lt;td>線上 schema 與 sharding 變更&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1&lt;/a>&lt;/td>
 &lt;td>BFCM 容量治理與 Game Day&lt;/td>
 &lt;td>把季節性峰值壓力轉成可預演、可回寫的年度可靠性節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">H2&lt;/a>&lt;/td>
 &lt;td>Pod Architecture 與 Resiliency Matrix&lt;/td>
 &lt;td>多租戶隔離與系統化失敗模式盤點&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Shopify 這個案例在講的是峰值流量如何被提前吸收，而不是在事故當下硬扛。讀者先抓 capacity planning、performance testing 與 pods architecture 的分工，再看它們怎麼把 BFCM 這種季節性壓力轉成可管理的工程節奏。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當流量會在短時間內暴增時，先做容量模型與壓測，再確認 pods 邊界能否切住故障擴散。當資料平台也在同一波壓力下成長時，重點不只在擴容，而在是否能保住查詢、寫入與回放的穩定節奏。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否在 peak 之前說出容量上限與安全緩衝&lt;/li>
&lt;li>能否把壓測結果對應到真實流量模型&lt;/li>
&lt;li>能否讓 pods 邊界成為故障隔離單位&lt;/li>
&lt;li>能否在高峰前完成演練與當日指揮節奏對齊&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Shopify 的價值在於它把峰值準備寫成年度節奏，這和 LinkedIn 的 capacity planning、AWS S3 的區域風險、Discord 的流量驚奇都能互相對照。讀這頁時要抓的是「先把峰值變成可預測問題」，而不是等事故來了才補救。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>BFCM 前的 capacity planning 讓峰值壓力先被模型吸收，而不是直接落在事故當下。&lt;/li>
&lt;li>pods architecture 把多租戶流量切成較小隔離單位，限制故障擴散。&lt;/li>
&lt;li>performance testing 讓真實峰值在演練階段就可見。&lt;/li>
&lt;li>resiliency tooling 讓團隊能在高峰前驗證失效模式。&lt;/li>
&lt;li>database resharding 讓高峰下的 stateful 系統仍能持續擴容。&lt;/li>
&lt;li>incident rehearsal 讓當日指揮與復原節奏先對齊。&lt;/li>
&lt;li>resiliency matrix 讓每個服務與失效模式都有明確對照。&lt;/li>
&lt;li>Toxiproxy 讓 TCP 層故障注入成為可重用工具。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://shopify.engineering/capacity-planning-shopify">Capacity Planning at Scale&lt;/a>：BFCM 前的容量規劃與驗證方法。&lt;/li>
&lt;li>&lt;a href="https://shopify.engineering/scale-performance-testing">Performance Testing At Scale—for BFCM and Beyond&lt;/a>：BFCM scale testing 與壓測節奏。&lt;/li>
&lt;li>&lt;a href="https://shopify.engineering/a-pods-architecture-to-allow-shopify-to-scale">A Pods Architecture To Allow Shopify To Scale&lt;/a>：pods 架構與隔離設計。&lt;/li>
&lt;li>&lt;a href="https://shopify.engineering/blogs/engineering/reliably-scale-data-platform">How to Reliably Scale Your Data Platform for High Volumes&lt;/a>：資料平台在高流量下的可靠性方法。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Shopify 是 BFCM（Black Friday / Cyber Monday）流量峰值的可靠性教學標竿、pod-based architecture 是 multi-tenant SaaS 的隔離典範。教學重點在「年度可預期峰值如何透過架構與演練準備」。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Pod-based Architecture：多租戶切分、商家隔離設計</li>
<li>BFCM 準備：年度峰值的 capacity planning 流程</li>
<li>Resiliency Matrix：列舉服務與失效模式的對照表</li>
<li>Toxiproxy / Resiliency tooling：Shopify 開源的 chaos 工具</li>
<li>Database sharding：MySQL 分片策略與 online resharding</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BFCM Capacity Planning</td>
          <td>容量預測、load test 設計、實際峰值對照</td>
      </tr>
      <tr>
          <td>Pod Architecture</td>
          <td>多租戶切分、failure isolation</td>
      </tr>
      <tr>
          <td>Resiliency Matrix</td>
          <td>失效模式對照表的維護方法</td>
      </tr>
      <tr>
          <td>Toxiproxy</td>
          <td>TCP-level 故障注入的工程實作</td>
      </tr>
      <tr>
          <td>Database resharding</td>
          <td>線上 schema 與 sharding 變更</td>
      </tr>
  </tbody>
</table>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1</a></td>
          <td>BFCM 容量治理與 Game Day</td>
          <td>把季節性峰值壓力轉成可預演、可回寫的年度可靠性節奏</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">H2</a></td>
          <td>Pod Architecture 與 Resiliency Matrix</td>
          <td>多租戶隔離與系統化失敗模式盤點</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Shopify 這個案例在講的是峰值流量如何被提前吸收，而不是在事故當下硬扛。讀者先抓 capacity planning、performance testing 與 pods architecture 的分工，再看它們怎麼把 BFCM 這種季節性壓力轉成可管理的工程節奏。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當流量會在短時間內暴增時，先做容量模型與壓測，再確認 pods 邊界能否切住故障擴散。當資料平台也在同一波壓力下成長時，重點不只在擴容，而在是否能保住查詢、寫入與回放的穩定節奏。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否在 peak 之前說出容量上限與安全緩衝</li>
<li>能否把壓測結果對應到真實流量模型</li>
<li>能否讓 pods 邊界成為故障隔離單位</li>
<li>能否在高峰前完成演練與當日指揮節奏對齊</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Shopify 的價值在於它把峰值準備寫成年度節奏，這和 LinkedIn 的 capacity planning、AWS S3 的區域風險、Discord 的流量驚奇都能互相對照。讀這頁時要抓的是「先把峰值變成可預測問題」，而不是等事故來了才補救。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>BFCM 前的 capacity planning 讓峰值壓力先被模型吸收，而不是直接落在事故當下。</li>
<li>pods architecture 把多租戶流量切成較小隔離單位，限制故障擴散。</li>
<li>performance testing 讓真實峰值在演練階段就可見。</li>
<li>resiliency tooling 讓團隊能在高峰前驗證失效模式。</li>
<li>database resharding 讓高峰下的 stateful 系統仍能持續擴容。</li>
<li>incident rehearsal 讓當日指揮與復原節奏先對齊。</li>
<li>resiliency matrix 讓每個服務與失效模式都有明確對照。</li>
<li>Toxiproxy 讓 TCP 層故障注入成為可重用工具。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/capacity-planning-shopify">Capacity Planning at Scale</a>：BFCM 前的容量規劃與驗證方法。</li>
<li><a href="https://shopify.engineering/scale-performance-testing">Performance Testing At Scale—for BFCM and Beyond</a>：BFCM scale testing 與壓測節奏。</li>
<li><a href="https://shopify.engineering/a-pods-architecture-to-allow-shopify-to-scale">A Pods Architecture To Allow Shopify To Scale</a>：pods 架構與隔離設計。</li>
<li><a href="https://shopify.engineering/blogs/engineering/reliably-scale-data-platform">How to Reliably Scale Your Data Platform for High Volumes</a>：資料平台在高流量下的可靠性方法。</li>
</ul>
]]></content:encoded></item><item><title>8.5 Twitch：直播與聊天室系統</title><link>https://tarrragon.github.io/blog/go/08-case-studies/twitch/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/twitch/</guid><description>&lt;p>Twitch 的案例幾乎就是 Go 教材裡高併發與即時系統的縮影。官方說法很直接：Go 被用在很多 busiest systems，上下文是 live video 與 chat，重點是 simplicity、safety、performance 與 readability。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/twitch">Twitch - Go’s march to low latency GC&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很適合低延遲、高事件量的即時系統。&lt;/li>
&lt;li>直播與聊天室會大量依賴長連線與狀態協調。&lt;/li>
&lt;li>可讀性在高壓力服務中仍然重要，因為維護者需要快速定位問題。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/case-studies">Go case studies page&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Twitch 的核心系統原始碼不是公開教學重點，所以這一章更適合把官方案例本身當成第一手材料，再回到你的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a>、channel 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a> 章節對照。&lt;/p></description><content:encoded><![CDATA[<p>Twitch 的案例幾乎就是 Go 教材裡高併發與即時系統的縮影。官方說法很直接：Go 被用在很多 busiest systems，上下文是 live video 與 chat，重點是 simplicity、safety、performance 與 readability。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/twitch">Twitch - Go’s march to low latency GC</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很適合低延遲、高事件量的即時系統。</li>
<li>直播與聊天室會大量依賴長連線與狀態協調。</li>
<li>可讀性在高壓力服務中仍然重要，因為維護者需要快速定位問題。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://go.dev/solutions/case-studies">Go case studies page</a></li>
</ul>
<p>Twitch 的核心系統原始碼不是公開教學重點，所以這一章更適合把官方案例本身當成第一手材料，再回到你的 <a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a>、channel 與 <a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 章節對照。</p>
]]></content:encoded></item><item><title>案例：資料結構選擇</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何選擇正確的資料結構來優化成員查詢效能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化&lt;/a>&lt;/li>
&lt;li>基本的時間複雜度概念（O(n)、O(1)）&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="成員查詢的效能差異">成員查詢的效能差異&lt;/h3>
&lt;p>在 Python 中，檢查某個元素是否存在於容器中（成員查詢）是最常見的操作之一：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">container&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="c1"># 做某些事&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但這行簡單的程式碼，背後的效能差異可能高達 &lt;strong>100 倍以上&lt;/strong>，取決於 &lt;code>container&lt;/code> 是什麼資料結構。&lt;/p>
&lt;h4 id="list-的成員查詢on">list 的成員查詢：O(n)&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">my_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&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="k">if&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">my_list&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># 最壞情況要檢查 n 個元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>list 是有序的線性結構，Python 必須從頭開始逐一比對，直到找到目標或走完整個 list。&lt;/p>
&lt;h4 id="set-的成員查詢o1">set 的成員查詢：O(1)&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">my_set&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&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="k">if&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">my_set&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># 平均只需要 1 次雜湊計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>set 使用雜湊表（hash table）實作，透過計算元素的雜湊值直接定位，不受容器大小影響。&lt;/p>
&lt;h3 id="真實案例測試檔案存在性檢查">真實案例：測試檔案存在性檢查&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>check_test_exists&lt;/code> 方法會檢查每個 Hook 是否有對應的測試檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查對應的測試檔案是否存在
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> hook_path: Hook 檔案路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[ValidationIssue]: 發現的問題
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &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>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 生成測試檔案名稱&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">test_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;test_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;-&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試檔案應該在 .claude/lib/tests/ 或 .claude/hooks/tests/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">possible_test_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">test_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">test_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">test_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">possible_test_paths&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">test_exists&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;info&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;未找到對應的測試檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;建議在以下位置建立測試:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; .claude/lib/tests/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">issues&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個程式碼有一個隱藏的效能問題：當需要驗證大量 Hook 時，每次都要呼叫 &lt;code>p.exists()&lt;/code> 進行檔案系統操作。&lt;/p>
&lt;p>假設我們要驗證 100 個 Hook，而測試目錄下有 200 個測試檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&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="s2">&amp;#34;&amp;#34;&amp;#34;驗證所有 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &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 class="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span> &lt;span class="c1"># 100 個 Hook&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hook_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&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">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&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="c1"># 每個 validate_hook 會呼叫 check_test_exists&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># check_test_exists 會呼叫 2 次 Path.exists()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 總共：100 * 2 = 200 次檔案系統操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="問題分析">問題分析&lt;/h3>
&lt;p>原始程式碼的瓶頸：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>重複的檔案系統操作&lt;/strong>：每個 Hook 都要呼叫 &lt;code>exists()&lt;/code> 檢查測試是否存在&lt;/li>
&lt;li>&lt;strong>沒有快取&lt;/strong>：相同的檢查可能被重複執行&lt;/li>
&lt;li>&lt;strong>線性搜尋&lt;/strong>：如果測試清單很長，用 list 儲存會導致 O(n) 的查詢時間&lt;/li>
&lt;/ol>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="用-set-取代-list">用 set 取代 list&lt;/h3>
&lt;p>核心思路：&lt;strong>先掃描一次測試目錄，建立測試檔案的 set，然後用 O(1) 查詢取代檔案系統操作&lt;/strong>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&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="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器（優化版）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&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="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&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 class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&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="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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 優化：預先建立測試檔案的 set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_files&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> 取得所有測試檔案名稱（快取）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> set[str]: 測試檔案名稱集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_scan_test_files&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_scan_test_files&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> 掃描所有測試目錄，建立測試檔案集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> set[str]: 測試檔案名稱集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">test_dirs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">test_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 使用 set 而非 list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">test_dir&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">test_dirs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">test_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">test_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">test_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test_*.py&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">test_files&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">test_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查對應的測試檔案是否存在（優化版）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 set 進行 O(1) 查詢，取代檔案系統操作。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="n">test_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;test_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;-&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 優化：O(1) 的 set 查詢，取代 O(n) 的 exists() 呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="n">test_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">test_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">test_exists&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;info&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;未找到對應的測試檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;建議在以下位置建立測試:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; .claude/lib/tests/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">issues&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1識別重複查詢">步驟 1：識別重複查詢&lt;/h4>
&lt;p>找出程式碼中哪些查詢會被重複執行：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何選擇正確的資料結構來優化成員查詢效能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化</a></li>
<li>基本的時間複雜度概念（O(n)、O(1)）</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="成員查詢的效能差異">成員查詢的效能差異</h3>
<p>在 Python 中，檢查某個元素是否存在於容器中（成員查詢）是最常見的操作之一：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">container</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 做某些事</span></span></span></code></pre></div><p>但這行簡單的程式碼，背後的效能差異可能高達 <strong>100 倍以上</strong>，取決於 <code>container</code> 是什麼資料結構。</p>
<h4 id="list-的成員查詢on">list 的成員查詢：O(n)</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">n</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">my_list</span><span class="p">:</span>  <span class="c1"># 最壞情況要檢查 n 個元素</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><p>list 是有序的線性結構，Python 必須從頭開始逐一比對，直到找到目標或走完整個 list。</p>
<h4 id="set-的成員查詢o1">set 的成員查詢：O(1)</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">my_set</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">n</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">my_set</span><span class="p">:</span>  <span class="c1"># 平均只需要 1 次雜湊計算</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><p>set 使用雜湊表（hash table）實作，透過計算元素的雜湊值直接定位，不受容器大小影響。</p>
<h3 id="真實案例測試檔案存在性檢查">真實案例：測試檔案存在性檢查</h3>
<p><code>hook_validator.py</code> 的 <code>check_test_exists</code> 方法會檢查每個 Hook 是否有對應的測試檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    檢查對應的測試檔案是否存在
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        hook_path: Hook 檔案路徑
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        list[ValidationIssue]: 發現的問題
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 生成測試檔案名稱</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">hook_name</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># 測試檔案應該在 .claude/lib/tests/ 或 .claude/hooks/tests/</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">possible_test_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">test_exists</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_test_paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">test_exists</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;未找到對應的測試檔案: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;建議在以下位置建立測試:</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;  .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><p>這個程式碼有一個隱藏的效能問題：當需要驗證大量 Hook 時，每次都要呼叫 <code>p.exists()</code> 進行檔案系統操作。</p>
<p>假設我們要驗證 100 個 Hook，而測試目錄下有 200 個測試檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證所有 Hook 檔案&#34;&#34;&#34;</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></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>  <span class="c1"># 100 個 Hook</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="c1"># 每個 validate_hook 會呼叫 check_test_exists</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># check_test_exists 會呼叫 2 次 Path.exists()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 總共：100 * 2 = 200 次檔案系統操作</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="問題分析">問題分析</h3>
<p>原始程式碼的瓶頸：</p>
<ol>
<li><strong>重複的檔案系統操作</strong>：每個 Hook 都要呼叫 <code>exists()</code> 檢查測試是否存在</li>
<li><strong>沒有快取</strong>：相同的檢查可能被重複執行</li>
<li><strong>線性搜尋</strong>：如果測試清單很長，用 list 儲存會導致 O(n) 的查詢時間</li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="用-set-取代-list">用 set 取代 list</h3>
<p>核心思路：<strong>先掃描一次測試目錄，建立測試檔案的 set，然後用 O(1) 查詢取代檔案系統操作</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器（優化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                <span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 優化：預先建立測試檔案的 set</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        取得所有測試檔案名稱（快取）
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">            set[str]: 測試檔案名稱集合
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_scan_test_files</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">_scan_test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        掃描所有測試目錄，建立測試檔案集合
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">            set[str]: 測試檔案名稱集合
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">test_dirs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># 使用 set 而非 list</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">for</span> <span class="n">test_dir</span> <span class="ow">in</span> <span class="n">test_dirs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">if</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="k">return</span> <span class="n">test_files</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">        檢查對應的測試檔案是否存在（優化版）
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">        使用 set 進行 O(1) 查詢，取代檔案系統操作。
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="c1"># 優化：O(1) 的 set 查詢，取代 O(n) 的 exists() 呼叫</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="n">test_exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">test_files</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">test_exists</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;未找到對應的測試檔案: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                        <span class="sa">f</span><span class="s2">&#34;建議在以下位置建立測試:</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="sa">f</span><span class="s2">&#34;  .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別重複查詢">步驟 1：識別重複查詢</h4>
<p>找出程式碼中哪些查詢會被重複執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 原始程式碼：每次驗證都會執行檔案系統操作</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hooks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># possible_test_paths 中的 Path.exists() 被呼叫 2 次</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">test_exists</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_test_paths</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2收集所有可能的查詢目標">步驟 2：收集所有可能的查詢目標</h4>
<p>在初始化時掃描一次，建立完整的資料集：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_scan_test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;一次性掃描所有測試檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">for</span> <span class="n">test_dir</span> <span class="ow">in</span> <span class="n">test_directories</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">test_files</span></span></span></code></pre></div><h4 id="步驟-3用-set-取代-list">步驟 3：用 set 取代 list</h4>
<p>確保查詢容器是 set 而非 list：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：用 list 儲存</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">test_files</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># O(n) 查詢</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">test_files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 正確：用 set 儲存</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># O(1) 查詢</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4加入快取機制">步驟 4：加入快取機制</h4>
<p>避免重複掃描：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;延遲初始化 + 快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_scan_test_files</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;需要時可以清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<p>讓我們用 <code>timeit</code> 測量 list 和 set 的查詢效能差異：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">string</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_membership_test</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較 list 和 set 的成員查詢效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 準備測試資料</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">generate_filename</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choices</span><span class="p">(</span><span class="n">string</span><span class="o">.</span><span class="n">ascii_lowercase</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">100</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">10000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">sizes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 建立測試資料</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">items</span> <span class="o">=</span> <span class="p">[</span><span class="n">generate_filename</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">size</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">test_list</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">test_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># 準備查詢目標（一半存在、一半不存在）</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">existing_items</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="n">size</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">non_existing_items</span> <span class="o">=</span> <span class="p">[</span><span class="n">generate_filename</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">query_items</span> <span class="o">=</span> <span class="n">existing_items</span> <span class="o">+</span> <span class="n">non_existing_items</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">random</span><span class="o">.</span><span class="n">shuffle</span><span class="p">(</span><span class="n">query_items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1"># 測量 list 查詢</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">def</span> <span class="nf">list_lookup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">query_items</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">_</span> <span class="o">=</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">test_list</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">list_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">list_lookup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># 測量 set 查詢</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">def</span> <span class="nf">set_lookup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">query_items</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="n">_</span> <span class="o">=</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">test_set</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">set_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">set_lookup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="c1"># 計算加速比</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">list_time</span> <span class="o">/</span> <span class="n">set_time</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">元素數量: </span><span class="si">{</span><span class="n">size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list 查詢: </span><span class="si">{</span><span class="n">list_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set 查詢:  </span><span class="si">{</span><span class="n">set_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比:    </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">benchmark_membership_test</span><span class="p">()</span></span></span></code></pre></div><p><strong>典型執行結果</strong>：</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">元素數量: 100
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  list 查詢: 0.0234 秒
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  set 查詢:  0.0012 秒
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  加速比:    19.5x
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">元素數量: 1,000
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  list 查詢: 0.2156 秒
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  set 查詢:  0.0013 秒
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  加速比:    165.8x
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">元素數量: 10,000
</span></span><span class="line"><span class="ln">12</span><span class="cl">  list 查詢: 2.1847 秒
</span></span><span class="line"><span class="ln">13</span><span class="cl">  set 查詢:  0.0014 秒
</span></span><span class="line"><span class="ln">14</span><span class="cl">  加速比:    1560.5x</span></span></code></pre></div><p><strong>觀察重點</strong>：</p>
<ol>
<li><strong>set 的查詢時間幾乎不變</strong>：無論元素數量是 100 還是 10,000，set 的查詢時間都在 0.001 秒左右</li>
<li><strong>list 的查詢時間線性增長</strong>：元素增加 10 倍，查詢時間也增加約 10 倍</li>
<li><strong>加速比隨資料量增加</strong>：元素越多，set 的優勢越明顯</li>
</ol>
<h3 id="實際場景測試">實際場景測試</h3>
<p>模擬 Hook 驗證器的真實使用場景：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Set</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_hook_validation</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬 Hook 驗證器的效能差異&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 模擬 200 個測試檔案</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">test_files_list</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;test_hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.py&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">200</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">test_files_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">test_files_list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 模擬 100 個 Hook 要檢查</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">hooks_to_check</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">check_with_list</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;使用 list 進行查詢&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">for</span> <span class="n">hook_name</span> <span class="ow">in</span> <span class="n">hooks_to_check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">test_files_list</span>  <span class="c1"># O(n)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">exists</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">check_with_set</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;使用 set 進行查詢&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">for</span> <span class="n">hook_name</span> <span class="ow">in</span> <span class="n">hooks_to_check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">test_files_set</span>  <span class="c1"># O(1)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">exists</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># 測量效能</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">list_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">check_with_list</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">set_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">check_with_set</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;驗證 100 個 Hook（執行 1000 次）：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list 版本: </span><span class="si">{</span><span class="n">list_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set 版本:  </span><span class="si">{</span><span class="n">set_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比:    </span><span class="si">{</span><span class="n">list_time</span> <span class="o">/</span> <span class="n">set_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">benchmark_hook_validation</span><span class="p">()</span></span></span></code></pre></div><p><strong>典型執行結果</strong>：</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">驗證 100 個 Hook（執行 1000 次）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  list 版本: 0.3892 秒
</span></span><span class="line"><span class="ln">3</span><span class="cl">  set 版本:  0.0087 秒
</span></span><span class="line"><span class="ln">4</span><span class="cl">  加速比:    44.7x</span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>資料結構</th>
          <th>查詢</th>
          <th>插入</th>
          <th>刪除</th>
          <th>記憶體</th>
          <th>有序</th>
          <th>重複元素</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>list</td>
          <td>O(n)</td>
          <td>O(1)*</td>
          <td>O(n)</td>
          <td>低</td>
          <td>是</td>
          <td>允許</td>
      </tr>
      <tr>
          <td>set</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>高</td>
          <td>否</td>
          <td>不允許</td>
      </tr>
      <tr>
          <td>dict</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>高</td>
          <td>是**</td>
          <td>鍵不允許</td>
      </tr>
  </tbody>
</table>
<p>*list 的 append 是 O(1)，insert 是 O(n)</p>
<p>**Python 3.7+ 的 dict 保持插入順序</p>
<h3 id="記憶體使用比較">記憶體使用比較</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">compare_memory</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較 list 和 set 的記憶體使用&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">items</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><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="n">list_version</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">set_version</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">list_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">list_version</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">set_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">set_version</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;10,000 個整數：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list: </span><span class="si">{</span><span class="n">list_size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set:  </span><span class="si">{</span><span class="n">set_size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set/list 比率: </span><span class="si">{</span><span class="n">set_size</span> <span class="o">/</span> <span class="n">list_size</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 輸出：</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 10,000 個整數：</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1">#   list: 87,624 bytes</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">#   set:  524,512 bytes</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">#   set/list 比率: 5.99x</span></span></span></code></pre></div><p><strong>權衡分析</strong>：</p>
<ul>
<li>set 的記憶體使用約為 list 的 4-8 倍</li>
<li>但查詢速度可以快 10-100 倍以上</li>
<li>對於大多數場景，記憶體增加可以接受</li>
</ul>
<h3 id="何時該用-list">何時該用 list？</h3>
<ol>
<li><strong>需要保持順序</strong>：元素的順序很重要</li>
<li><strong>需要重複元素</strong>：同一個值可能出現多次</li>
<li><strong>需要索引存取</strong>：經常用 <code>items[i]</code> 存取</li>
<li><strong>主要操作是遍歷</strong>：很少做成員查詢</li>
<li><strong>資料量很小</strong>：少於 10 個元素時差異不大</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 適合用 list 的場景</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">commands</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;init&#34;</span><span class="p">,</span> <span class="s2">&#34;build&#34;</span><span class="p">,</span> <span class="s2">&#34;test&#34;</span><span class="p">,</span> <span class="s2">&#34;deploy&#34;</span><span class="p">]</span>  <span class="c1"># 需要順序</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">scores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">95</span><span class="p">,</span> <span class="mi">87</span><span class="p">,</span> <span class="mi">95</span><span class="p">,</span> <span class="mi">92</span><span class="p">,</span> <span class="mi">87</span><span class="p">]</span>  <span class="c1"># 需要重複</span></span></span></code></pre></div><h3 id="何時該用-set">何時該用 set？</h3>
<ol>
<li><strong>主要操作是成員查詢</strong>：頻繁使用 <code>in</code> 運算子</li>
<li><strong>需要去重</strong>：自動排除重複元素</li>
<li><strong>需要集合運算</strong>：交集、聯集、差集</li>
<li><strong>資料量較大</strong>：幾百個以上的元素</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 適合用 set 的場景</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">valid_extensions</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;.py&#34;</span><span class="p">,</span> <span class="s2">&#34;.js&#34;</span><span class="p">,</span> <span class="s2">&#34;.ts&#34;</span><span class="p">}</span>  <span class="c1"># 成員查詢</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">seen_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># 追蹤已處理的檔案（去重）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">common_tags</span> <span class="o">=</span> <span class="n">tags_a</span> <span class="o">&amp;</span> <span class="n">tags_b</span>  <span class="c1"># 集合運算</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用的情況">適合使用的情況</h3>
<ol>
<li><strong>頻繁的成員查詢</strong>：程式碼中有很多 <code>if x in container</code> 的檢查</li>
<li><strong>查詢容器較大</strong>：容器有幾百個以上的元素</li>
<li><strong>查詢頻率高</strong>：同一個容器被查詢多次</li>
<li><strong>不需要元素順序</strong>：只關心「有沒有」而非「在哪裡」</li>
</ol>
<h3 id="不建議使用的情況">不建議使用的情況</h3>
<ol>
<li><strong>需要保持插入順序</strong>：用 list 或 dict</li>
<li><strong>需要重複元素</strong>：用 list 或 collections.Counter</li>
<li><strong>容器很小</strong>：10 個以下元素時差異不明顯</li>
<li><strong>元素不可雜湊</strong>：list、dict 等 mutable 物件無法放入 set</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不可雜湊的物件無法用 set</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]]</span>  <span class="c1"># list of lists</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># set(items)  # TypeError: unhashable type: &#39;list&#39;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 解法：轉換為 tuple</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">items_set</span> <span class="o">=</span> <span class="p">{</span><span class="nb">tuple</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">}</span>  <span class="c1"># OK</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>實作去重函式</strong>：寫一個函式，用 set 去除 list 中的重複元素，同時保持原本的順序</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">deduplicate_ordered</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    去除重複元素，保持順序
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        &gt;&gt;&gt; deduplicate_ordered([3, 1, 2, 1, 3, 2])
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        [3, 1, 2]
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><ol start="2">
<li><strong>優化單字計數器</strong>：以下程式碼檢查一段文字中有多少個「停用詞」，請用 set 優化它</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">STOP_WORDS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;the&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="s2">&#34;an&#34;</span><span class="p">,</span> <span class="s2">&#34;is&#34;</span><span class="p">,</span> <span class="s2">&#34;are&#34;</span><span class="p">,</span> <span class="s2">&#34;was&#34;</span><span class="p">,</span> <span class="s2">&#34;were&#34;</span><span class="p">,</span> <span class="s2">&#34;be&#34;</span><span class="p">,</span> <span class="s2">&#34;been&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">              <span class="s2">&#34;being&#34;</span><span class="p">,</span> <span class="s2">&#34;have&#34;</span><span class="p">,</span> <span class="s2">&#34;has&#34;</span><span class="p">,</span> <span class="s2">&#34;had&#34;</span><span class="p">,</span> <span class="s2">&#34;do&#34;</span><span class="p">,</span> <span class="s2">&#34;does&#34;</span><span class="p">,</span> <span class="s2">&#34;did&#34;</span><span class="p">,</span> <span class="s2">&#34;will&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">              <span class="s2">&#34;would&#34;</span><span class="p">,</span> <span class="s2">&#34;could&#34;</span><span class="p">,</span> <span class="s2">&#34;should&#34;</span><span class="p">,</span> <span class="s2">&#34;may&#34;</span><span class="p">,</span> <span class="s2">&#34;might&#34;</span><span class="p">,</span> <span class="s2">&#34;must&#34;</span><span class="p">,</span> <span class="s2">&#34;can&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">count_stop_words_slow</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;計算停用詞數量（待優化）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">if</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">STOP_WORDS</span><span class="p">:</span>  <span class="c1"># O(n) 查詢</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 請優化這個函式</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">count_stop_words_fast</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;計算停用詞數量（優化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<ol start="3">
<li><strong>實作檔案比對工具</strong>：比較兩個目錄中的檔案，找出只在其中一個目錄存在的檔案</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">compare_directories</span><span class="p">(</span><span class="n">dir1</span><span class="p">:</span> <span class="n">Path</span><span class="p">,</span> <span class="n">dir2</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    比較兩個目錄的檔案
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        dict: {
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">            &#34;only_in_dir1&#34;: set[str],
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">            &#34;only_in_dir2&#34;: set[str],
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">            &#34;in_both&#34;: set[str],
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    提示：使用 set 的交集、差集運算
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><ol start="4">
<li><strong>優化 Hook 驗證器</strong>：參考本文的優化方案，為 <code>HookValidator</code> 加入以下功能：</li>
</ol>
<ul>
<li>快取測試檔案列表</li>
<li>支援多個測試目錄</li>
<li>在測試目錄變更時自動更新快取</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></em>
<em>返回：<a href="/blog/python-advanced/08-practical-optimization/case-studies/" data-link-title="案例研究：效能優化實戰" data-link-desc="基於 .claude/lib 的效能優化實戰案例">案例研究索引</a></em></p>
]]></content:encoded></item><item><title>9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/</guid><description>&lt;p>這個案例的核心責任是說明「cache layer 在持續成長服務」的角色 — 不是峰值問題、是延遲 SLA 與成本曲線同時拉緊的長期工程議題。Tinder 的配對引擎需要在每次滑動都查多個快取（用戶 profile、距離、偏好過濾、推薦池），單次互動的延遲就是 UX 本身。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Tinder 在 ElastiCache for Valkey 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers&lt;/a>）：&lt;/p>
&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>約 4700 萬 MAU (2025)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>配對累計&lt;/td>
 &lt;td>超過 10 億次配對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>地理覆蓋&lt;/td>
 &lt;td>190 個國家&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務年數&lt;/td>
 &lt;td>自 2012 年起&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲特性&lt;/td>
 &lt;td>sub-millisecond latency&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>ElastiCache for Redis 7.1 在 r7g.4xlarge 上可達單節點 100 萬 RPS、單 cluster 5 億 RPS（引自 &lt;a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">AWS Database Blog&lt;/a>）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Tinder 案例值得讀的是「快取在 long-running 服務的角色變化」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>快取不是 DB 的補救、是主要服務面&lt;/strong>：配對引擎每次互動讀 cache 不讀 DB、cache miss 是 &lt;em>邊緣案例&lt;/em>。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache-as-source-of-truth 與 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a> 設計。&lt;/li>
&lt;li>&lt;strong>次毫秒延遲是業務 KPI、不只是技術指標&lt;/strong>：手指滑動之後 250ms 內必須給結果、否則「卡頓」。中間整個 chain（網路、cache、序列化）的 latency budget 必須緊。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;li>&lt;strong>長期 sustained growth 的容量曲線是成本曲線&lt;/strong>：47M MAU 沒有明顯峰谷、容量規劃變成「每月線性擴容 X%」的長期決策、不是峰值規劃。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的長期成本工程。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Tinder 的「configurable matching」業務邏輯複雜、快取資料的 schema 變化頻繁。一個 schema 變更可能讓既有 cache 全部 invalid、引發 cache stampede。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback&lt;/a>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>cache layer 容量規劃跟 DB 容量規劃要分開&lt;/strong>：cache 容量受 working set size 影響、DB 容量受 total dataset 影響、兩者擴容邏輯不一樣。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache sizing。&lt;/li>
&lt;li>&lt;strong>cache 命中率變化是業務變化的訊號&lt;/strong>：突然命中率掉、可能是新功能影響 access pattern、不一定是 cache 容量問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 的訊號治理。&lt;/li>
&lt;li>&lt;strong>Valkey vs Redis OSS vs MemoryDB 是不同 trade-off&lt;/strong>：Valkey（社群分支、AWS 主推）、Redis OSS（受授權變化影響）、MemoryDB（持久化）三者選擇影響長期 vendor lock-in。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Memorystore for Redis / Valkey、Azure Cache for Redis、自建 Redis Cluster + Sentinel 都可以實作對等架構。差異是 vendor 的 patch cadence 與容量擴張流程。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「cache layer 在持續成長服務」的角色 — 不是峰值問題、是延遲 SLA 與成本曲線同時拉緊的長期工程議題。Tinder 的配對引擎需要在每次滑動都查多個快取（用戶 profile、距離、偏好過濾、推薦池），單次互動的延遲就是 UX 本身。</p>
<h2 id="觀察">觀察</h2>
<p>Tinder 在 ElastiCache for Valkey 的關鍵數字（引自 <a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活用戶</td>
          <td>約 4700 萬 MAU (2025)</td>
      </tr>
      <tr>
          <td>配對累計</td>
          <td>超過 10 億次配對</td>
      </tr>
      <tr>
          <td>地理覆蓋</td>
          <td>190 個國家</td>
      </tr>
      <tr>
          <td>服務年數</td>
          <td>自 2012 年起</td>
      </tr>
      <tr>
          <td>延遲特性</td>
          <td>sub-millisecond latency</td>
      </tr>
  </tbody>
</table>
<p>ElastiCache for Redis 7.1 在 r7g.4xlarge 上可達單節點 100 萬 RPS、單 cluster 5 億 RPS（引自 <a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">AWS Database Blog</a>）。</p>
<h2 id="判讀">判讀</h2>
<p>Tinder 案例值得讀的是「快取在 long-running 服務的角色變化」。</p>
<ol>
<li><strong>快取不是 DB 的補救、是主要服務面</strong>：配對引擎每次互動讀 cache 不讀 DB、cache miss 是 <em>邊緣案例</em>。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache-as-source-of-truth 與 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a> 設計。</li>
<li><strong>次毫秒延遲是業務 KPI、不只是技術指標</strong>：手指滑動之後 250ms 內必須給結果、否則「卡頓」。中間整個 chain（網路、cache、序列化）的 latency budget 必須緊。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
<li><strong>長期 sustained growth 的容量曲線是成本曲線</strong>：47M MAU 沒有明顯峰谷、容量規劃變成「每月線性擴容 X%」的長期決策、不是峰值規劃。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的長期成本工程。</li>
</ol>
<p>需要警惕：Tinder 的「configurable matching」業務邏輯複雜、快取資料的 schema 變化頻繁。一個 schema 變更可能讓既有 cache 全部 invalid、引發 cache stampede。對應 <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback</a>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>cache layer 容量規劃跟 DB 容量規劃要分開</strong>：cache 容量受 working set size 影響、DB 容量受 total dataset 影響、兩者擴容邏輯不一樣。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache sizing。</li>
<li><strong>cache 命中率變化是業務變化的訊號</strong>：突然命中率掉、可能是新功能影響 access pattern、不一定是 cache 容量問題。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 的訊號治理。</li>
<li><strong>Valkey vs Redis OSS vs MemoryDB 是不同 trade-off</strong>：Valkey（社群分支、AWS 主推）、Redis OSS（受授權變化影響）、MemoryDB（持久化）三者選擇影響長期 vendor lock-in。</li>
</ol>
<p>跨平台等效：GCP Memorystore for Redis / Valkey、Azure Cache for Redis、自建 Redis Cluster + Sentinel 都可以實作對等架構。差異是 vendor 的 patch cadence 與容量擴張流程。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 cache layer 容量 → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想做 latency budget 反推 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a></li>
<li>想理解 cache stampede 風險 → <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback</a></li>
<li>對照其他 cache 案例 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB</a>（KV 高吞吐）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/elasticache/customers/">Amazon ElastiCache Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">Achieve over 500 million requests per second per cluster with ElastiCache for Redis 7.1</a></li>
<li><a href="https://aws.amazon.com/blogs/database/optimize-redis-client-performance-for-amazon-elasticache/">Optimize Redis Client Performance for ElastiCache and MemoryDB</a></li>
</ul>
]]></content:encoded></item><item><title>2.C6 Netflix：EVCache 全域快取層</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/netflix-evcache-global-cache-layer/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/netflix-evcache-global-cache-layer/</guid><description>&lt;p>這個案例的核心責任是說明快取在全球服務下會變成平台能力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Netflix 用 EVCache 支撐大規模低延遲讀取，把快取從單服務實作提升為共用基礎設施。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當讀取延遲目標很嚴格且區域分布廣，快取需要跨區一致性與故障容忍設計。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>平台化快取客戶端與治理規則。&lt;/li>
&lt;li>把失效策略與區域容錯納入同一模型。&lt;/li>
&lt;li>以可觀測指標評估命中率與恢復能力。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://netflixtechblog.com/caching-for-a-global-netflix-7bcc457012f1">EVCache&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取在全球服務下會變成平台能力。</p>
<h2 id="觀察">觀察</h2>
<p>Netflix 用 EVCache 支撐大規模低延遲讀取，把快取從單服務實作提升為共用基礎設施。</p>
<h2 id="判讀">判讀</h2>
<p>當讀取延遲目標很嚴格且區域分布廣，快取需要跨區一致性與故障容忍設計。</p>
<h2 id="策略">策略</h2>
<ol>
<li>平台化快取客戶端與治理規則。</li>
<li>把失效策略與區域容錯納入同一模型。</li>
<li>以可觀測指標評估命中率與恢復能力。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/high-concurrency-access/" data-link-title="2.1 高併發下的 Redis 讀寫邊界" data-link-desc="說明高併發服務如何共用 Redis client、控制 pipeline 與避免 cache stampede">2.1</a> 與 <a href="/blog/backend/00-service-selection/failure-observability-design/" data-link-title="0.7 錯誤定位、觀測訊號與備援切換設計" data-link-desc="從錯誤分類、定位線索、降級策略與 failover 設計服務可維護性">0.7</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://netflixtechblog.com/caching-for-a-global-netflix-7bcc457012f1">EVCache</a></li>
</ul>
]]></content:encoded></item><item><title>3.C6 Uber：Kafka 事件平台演進</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/</guid><description>&lt;p>Uber 的 Kafka 演進案例揭露了 MQ 從「幾個團隊自管的 broker」到「全公司共享的事件平台」的治理轉折點。轉折的核心判斷是：規模化之後，broker 容量擴展的成本小於 workload 治理缺失的成本。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Uber 的事件流涵蓋行程追蹤、司機定位、計費事件、推播通知、即時定價、ETA 計算跟 analytics。早期各團隊各自架設 Kafka 叢集，隨著 Kafka 在 Uber 內部的採用率上升，叢集數量跟 topic 數量快速增長，但沒有統一的治理。&lt;/p>
&lt;p>Uber 的 Kafka 規模峰值達到每秒數百萬筆訊息、數十個叢集、數千個 topic。在這個規模下，管理壓力從「單一叢集的 broker 夠不夠」轉到「誰在用、用多少、怎麼收費、故障時誰負責」。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="團隊自管的碎片化">團隊自管的碎片化&lt;/h3>
&lt;p>各團隊各自架設 Kafka 時，每個叢集的版本、配置、監控、備份策略都不同。運維知識散落在各團隊，沒有共享的 runbook 或值班流程。某個團隊的 Kafka 出問題時，其他團隊幫不上忙；知識在人員流動時遺失。&lt;/p>
&lt;p>碎片化的另一個後果是資源浪費。每個團隊各自預留的容量加總起來遠大於集中管理所需。低流量團隊的叢集常年使用率低於 10%，但因為自管模式下沒有共享容量的機制，資源無法調配。&lt;/p>
&lt;h3 id="topic-爆炸與無主-topic">Topic 爆炸與無主 topic&lt;/h3>
&lt;p>沒有 topic 建立的治理流程時，任何人都可以建 topic。Topic 的命名不一致、retention 設定不一致、owner 不明。離職的工程師建立的 topic 仍在接收資料、佔用 broker 資源，但沒人知道這些 topic 服務什麼業務。&lt;/p>
&lt;p>LinkedIn 後來也遇到同樣的問題並開發了 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">TopicGC&lt;/a> 做 topic 生命週期管理。Uber 的解法路線類似 — 把 topic 建立變成需要 owner、retention policy 跟業務標籤的審核流程。&lt;/p>
&lt;h3 id="故障排查的責任不清">故障排查的責任不清&lt;/h3>
&lt;p>叢集內的故障（broker OOM、partition leader 不均衡、consumer lag spike）需要 Kafka 專業知識排查。團隊自管模式下，每個團隊都需要一定程度的 Kafka 運維能力，但多數團隊的核心能力是業務邏輯而非 MQ 運維。&lt;/p>
&lt;p>故障排查的慣性是「先問 Kafka 團隊有沒有人可以幫忙」— 但沒有正式的 Kafka 團隊，所以問的是「上次修過 Kafka 的那個人」。&lt;/p>
&lt;h2 id="解法平台化">解法：平台化&lt;/h2>
&lt;p>Uber 的解法是把 Kafka 從分散自管收斂到集中平台 — 一個專責的 Kafka platform team 統一管理所有叢集、提供標準化的使用介面。&lt;/p>
&lt;h3 id="多租戶治理">多租戶治理&lt;/h3>
&lt;p>平台化的核心是多租戶模型 — 每個業務團隊是一個 tenant，tenant 有 quota（ingestion rate、partition 數量上限、retention 上限）跟 cost attribution。&lt;/p>
&lt;p>Quota 的目的是防止單一 tenant 的爆量拖累整個平台。Cost attribution 的目的是讓 tenant 看到自己的用量跟成本，驅動合理使用。&lt;/p>
&lt;h3 id="標準化-topic-管理">標準化 topic 管理&lt;/h3>
&lt;p>Topic 的建立走 self-service portal — 團隊填寫 owner、業務用途、預估流量、retention 需求，portal 自動配置 topic 並建立監控。沒有 owner 的 topic 不允許建立；owner 離職時 topic 需要交接或標記為候選淘汰。&lt;/p>
&lt;h3 id="統一監控與值班">統一監控與值班&lt;/h3>
&lt;p>Platform team 統一監控所有叢集的 broker 健康（replication lag、under-replicated partitions、disk usage、CPU），提供共用的 dashboard 跟 alert。值班由 platform team 負責 broker 層面的問題，業務層面的問題（consumer 設計錯誤、message 格式不對）由各 tenant team 自行處理。&lt;/p>
&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>低到中（受 quota 跟 policy 約束）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>運維負擔分配&lt;/td>
 &lt;td>分散（每個團隊各自負擔）&lt;/td>
 &lt;td>集中（platform team 吸收 broker 層）&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>大（共享平台故障影響所有 tenant）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>專業知識需求&lt;/td>
 &lt;td>每個團隊都要一些 Kafka 運維知識&lt;/td>
 &lt;td>集中在 platform team&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>平台化的最大風險是共享平台成為單點 — broker 故障影響所有 tenant。Uber 用跟 LinkedIn 類似的分層叢集策略（critical vs best-effort）降低共享風險，但這也讓平台的運維複雜度上升。&lt;/p></description><content:encoded><![CDATA[<p>Uber 的 Kafka 演進案例揭露了 MQ 從「幾個團隊自管的 broker」到「全公司共享的事件平台」的治理轉折點。轉折的核心判斷是：規模化之後，broker 容量擴展的成本小於 workload 治理缺失的成本。</p>
<h2 id="業務背景">業務背景</h2>
<p>Uber 的事件流涵蓋行程追蹤、司機定位、計費事件、推播通知、即時定價、ETA 計算跟 analytics。早期各團隊各自架設 Kafka 叢集，隨著 Kafka 在 Uber 內部的採用率上升，叢集數量跟 topic 數量快速增長，但沒有統一的治理。</p>
<p>Uber 的 Kafka 規模峰值達到每秒數百萬筆訊息、數十個叢集、數千個 topic。在這個規模下，管理壓力從「單一叢集的 broker 夠不夠」轉到「誰在用、用多少、怎麼收費、故障時誰負責」。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="團隊自管的碎片化">團隊自管的碎片化</h3>
<p>各團隊各自架設 Kafka 時，每個叢集的版本、配置、監控、備份策略都不同。運維知識散落在各團隊，沒有共享的 runbook 或值班流程。某個團隊的 Kafka 出問題時，其他團隊幫不上忙；知識在人員流動時遺失。</p>
<p>碎片化的另一個後果是資源浪費。每個團隊各自預留的容量加總起來遠大於集中管理所需。低流量團隊的叢集常年使用率低於 10%，但因為自管模式下沒有共享容量的機制，資源無法調配。</p>
<h3 id="topic-爆炸與無主-topic">Topic 爆炸與無主 topic</h3>
<p>沒有 topic 建立的治理流程時，任何人都可以建 topic。Topic 的命名不一致、retention 設定不一致、owner 不明。離職的工程師建立的 topic 仍在接收資料、佔用 broker 資源，但沒人知道這些 topic 服務什麼業務。</p>
<p>LinkedIn 後來也遇到同樣的問題並開發了 <a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">TopicGC</a> 做 topic 生命週期管理。Uber 的解法路線類似 — 把 topic 建立變成需要 owner、retention policy 跟業務標籤的審核流程。</p>
<h3 id="故障排查的責任不清">故障排查的責任不清</h3>
<p>叢集內的故障（broker OOM、partition leader 不均衡、consumer lag spike）需要 Kafka 專業知識排查。團隊自管模式下，每個團隊都需要一定程度的 Kafka 運維能力，但多數團隊的核心能力是業務邏輯而非 MQ 運維。</p>
<p>故障排查的慣性是「先問 Kafka 團隊有沒有人可以幫忙」— 但沒有正式的 Kafka 團隊，所以問的是「上次修過 Kafka 的那個人」。</p>
<h2 id="解法平台化">解法：平台化</h2>
<p>Uber 的解法是把 Kafka 從分散自管收斂到集中平台 — 一個專責的 Kafka platform team 統一管理所有叢集、提供標準化的使用介面。</p>
<h3 id="多租戶治理">多租戶治理</h3>
<p>平台化的核心是多租戶模型 — 每個業務團隊是一個 tenant，tenant 有 quota（ingestion rate、partition 數量上限、retention 上限）跟 cost attribution。</p>
<p>Quota 的目的是防止單一 tenant 的爆量拖累整個平台。Cost attribution 的目的是讓 tenant 看到自己的用量跟成本，驅動合理使用。</p>
<h3 id="標準化-topic-管理">標準化 topic 管理</h3>
<p>Topic 的建立走 self-service portal — 團隊填寫 owner、業務用途、預估流量、retention 需求，portal 自動配置 topic 並建立監控。沒有 owner 的 topic 不允許建立；owner 離職時 topic 需要交接或標記為候選淘汰。</p>
<h3 id="統一監控與值班">統一監控與值班</h3>
<p>Platform team 統一監控所有叢集的 broker 健康（replication lag、under-replicated partitions、disk usage、CPU），提供共用的 dashboard 跟 alert。值班由 platform team 負責 broker 層面的問題，業務層面的問題（consumer 設計錯誤、message 格式不對）由各 tenant team 自行處理。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>團隊自管</th>
          <th>平台化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自主性</td>
          <td>高（團隊想怎麼配就怎麼配）</td>
          <td>低到中（受 quota 跟 policy 約束）</td>
      </tr>
      <tr>
          <td>運維負擔分配</td>
          <td>分散（每個團隊各自負擔）</td>
          <td>集中（platform team 吸收 broker 層）</td>
      </tr>
      <tr>
          <td>資源利用率</td>
          <td>低（各自預留、無法共用）</td>
          <td>高（共享容量、動態分配）</td>
      </tr>
      <tr>
          <td>治理一致性</td>
          <td>低（版本、配置、命名各自為政）</td>
          <td>高（統一版本、統一配置標準）</td>
      </tr>
      <tr>
          <td>故障影響面</td>
          <td>小（自管叢集只影響自己的團隊）</td>
          <td>大（共享平台故障影響所有 tenant）</td>
      </tr>
      <tr>
          <td>專業知識需求</td>
          <td>每個團隊都要一些 Kafka 運維知識</td>
          <td>集中在 platform team</td>
      </tr>
  </tbody>
</table>
<p>平台化的最大風險是共享平台成為單點 — broker 故障影響所有 tenant。Uber 用跟 LinkedIn 類似的分層叢集策略（critical vs best-effort）降低共享風險，但這也讓平台的運維複雜度上升。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 容量規劃跟 topic 管理的基礎。</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：共享 Kafka 平台作為 dependency，tenant team 的 reliability budget 怎麼計算。</li>
<li><a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a>：平台化後 consumer 設計的規範跟限制。</li>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 cost attribution</a>：平台成本歸因到 tenant 的做法。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>組織內有 3 個以上團隊各自架設 Kafka、版本跟配置不統一</li>
<li>Topic 數量持續增長但沒人能說清楚哪些 topic 還在用</li>
<li>故障排查依賴特定個人而非共用的 runbook</li>
<li>叢集資源利用率低但各團隊仍要求擴容</li>
<li>管理層問「Kafka 總共花多少錢、誰在用」但沒人能回答</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.uber.com/en-TW/blog/kafka/">Building Uber&rsquo;s Kafka Infrastructure</a></li>
</ul>
]]></content:encoded></item><item><title>4.C6 AWS：ADOT on EKS 管線遷移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/</guid><description>&lt;p>這個案例的核心責任是把 observability 遷移做成管線治理，而不是單點 agent 替換。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>AWS ADOT on EKS 的實務把 metrics、traces 採集策略整合到可管理的 collector pipeline。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>多代理混用雖然能運作，但在規模化時會放大配置漂移與維運成本。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先統一 collector 部署模式。&lt;/li>
&lt;li>將 exporter 與 sampling 規則集中管理。&lt;/li>
&lt;li>以資料品質指標驗證遷移成效。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws-otel.github.io/docs/getting-started/adot-eks-add-on/">AWS Distro for OpenTelemetry on EKS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 observability 遷移做成管線治理，而不是單點 agent 替換。</p>
<h2 id="觀察">觀察</h2>
<p>AWS ADOT on EKS 的實務把 metrics、traces 採集策略整合到可管理的 collector pipeline。</p>
<h2 id="判讀">判讀</h2>
<p>多代理混用雖然能運作，但在規模化時會放大配置漂移與維運成本。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先統一 collector 部署模式。</li>
<li>將 exporter 與 sampling 規則集中管理。</li>
<li>以資料品質指標驗證遷移成效。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws-otel.github.io/docs/getting-started/adot-eks-add-on/">AWS Distro for OpenTelemetry on EKS</a></li>
</ul>
]]></content:encoded></item><item><title>5.C6 Airbnb：Kubernetes 叢集擴縮演進</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-kubernetes-cluster-scaling-evolution/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-kubernetes-cluster-scaling-evolution/</guid><description>&lt;p>這個案例的核心責任是說明部署平台演進常來自容量治理需求。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 的叢集擴縮經歷了多個演進階段。早期是手動調整 node 數量——工程師根據流量預測或事故壓力臨時加 node、事後忘記縮回。中期引入 Cluster Autoscaler，讓 node 數量跟 pending pod 連動。後期隨工作負載類型分化（stateless API、長連線服務、batch job、ML 訓練），單一 autoscaler policy 無法覆蓋所有場景，開始分群治理。&lt;/p>
&lt;p>這個演進路徑的共同主題是「每當流量型態或 workload 組成改變，原本的擴縮策略就會在某個量級開始失效」。擴縮策略的有效期跟服務演進速度成反比。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>叢集擴縮若停留在人工流程，面對高波動流量會放大成本與可用性風險。人工擴縮的問題有兩面：反應太慢（流量已衝高但 node 還沒加上來）和撤退太慢（流量已回落但多餘 node 繼續燒錢）。自動化解決反應速度，但引入新的判讀問題——autoscaler 的參數設定本身需要治理。&lt;/p>
&lt;p>HPA 觸發閾值設太低會造成 pod 數量頻繁抖動；Cluster Autoscaler 的 scale-down delay 設太短會在流量波動時反覆 add/remove node，增加 pod eviction 頻率。這些參數的調校要依 workload 類型分群——API 服務的擴縮節奏跟 batch job 完全不同。&lt;/p>
&lt;p>另一個判讀是擴縮策略跟事故指標要綁定。autoscaler 的動作（scale-up trigger、scale-down execution、node provision latency）如果不在事故 timeline 上可見，事故團隊無法分辨「是 autoscaler 來不及」還是「是應用本身有問題」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>擴縮策略版本化與可回放&lt;/strong>：HPA / VPA / Cluster Autoscaler / Karpenter 的配置進 git，變更走 release flow。每次調參都有 commit 紀錄，事故後可以追溯「這次 scale-down 過快是因為哪次參數變更」。版本化的另一個價值是可回放——新的擴縮配置在 staging 環境用歷史流量 replay 驗證後，再推到 production。&lt;/li>
&lt;li>&lt;strong>workload 分群擴縮&lt;/strong>：stateless API 用 CPU / RPS-based HPA、batch job 用 queue depth-based HPA、長連線服務用 connection count-based 自訂 metric。不同 workload 類型放在不同 namespace，各自有獨立的 autoscaler policy。避免一套 HPA 規則套全部 workload。&lt;/li>
&lt;li>&lt;strong>容量治理與事故指標綁定&lt;/strong>：HPA 觸發事件、Cluster Autoscaler 的 scale-up / scale-down 事件、node provision latency 都送進事故 timeline（可用 Kubernetes event exporter 或 custom metric）。事故 timeline 上看到「HPA 觸發後 3 分鐘 node 才 ready」就能直接判斷「容量補充太慢」而非「應用有 bug」。&lt;/li>
&lt;/ol>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>擴縮策略變更的回退比應用版本回退簡單——改 HPA / autoscaler 的 config 就好。風險在於回退後的舊策略可能已經跟當前 workload 型態不匹配（workload 成長了、流量特性變了）。穩定做法是回退後立刻進入觀察窗口，確認舊策略在當前流量下仍然有效。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment&lt;/a> 看 autoscaling 與部署策略協同。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 platform lifecycle contract&lt;/a> 看不同 workload 的 lifecycle 差異如何影響擴縮設計。回 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity &amp;amp; cost&lt;/a> 看容量規劃的完整框架。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb&lt;/a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明部署平台演進常來自容量治理需求。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 的叢集擴縮經歷了多個演進階段。早期是手動調整 node 數量——工程師根據流量預測或事故壓力臨時加 node、事後忘記縮回。中期引入 Cluster Autoscaler，讓 node 數量跟 pending pod 連動。後期隨工作負載類型分化（stateless API、長連線服務、batch job、ML 訓練），單一 autoscaler policy 無法覆蓋所有場景，開始分群治理。</p>
<p>這個演進路徑的共同主題是「每當流量型態或 workload 組成改變，原本的擴縮策略就會在某個量級開始失效」。擴縮策略的有效期跟服務演進速度成反比。</p>
<h2 id="判讀">判讀</h2>
<p>叢集擴縮若停留在人工流程，面對高波動流量會放大成本與可用性風險。人工擴縮的問題有兩面：反應太慢（流量已衝高但 node 還沒加上來）和撤退太慢（流量已回落但多餘 node 繼續燒錢）。自動化解決反應速度，但引入新的判讀問題——autoscaler 的參數設定本身需要治理。</p>
<p>HPA 觸發閾值設太低會造成 pod 數量頻繁抖動；Cluster Autoscaler 的 scale-down delay 設太短會在流量波動時反覆 add/remove node，增加 pod eviction 頻率。這些參數的調校要依 workload 類型分群——API 服務的擴縮節奏跟 batch job 完全不同。</p>
<p>另一個判讀是擴縮策略跟事故指標要綁定。autoscaler 的動作（scale-up trigger、scale-down execution、node provision latency）如果不在事故 timeline 上可見，事故團隊無法分辨「是 autoscaler 來不及」還是「是應用本身有問題」。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>擴縮策略版本化與可回放</strong>：HPA / VPA / Cluster Autoscaler / Karpenter 的配置進 git，變更走 release flow。每次調參都有 commit 紀錄，事故後可以追溯「這次 scale-down 過快是因為哪次參數變更」。版本化的另一個價值是可回放——新的擴縮配置在 staging 環境用歷史流量 replay 驗證後，再推到 production。</li>
<li><strong>workload 分群擴縮</strong>：stateless API 用 CPU / RPS-based HPA、batch job 用 queue depth-based HPA、長連線服務用 connection count-based 自訂 metric。不同 workload 類型放在不同 namespace，各自有獨立的 autoscaler policy。避免一套 HPA 規則套全部 workload。</li>
<li><strong>容量治理與事故指標綁定</strong>：HPA 觸發事件、Cluster Autoscaler 的 scale-up / scale-down 事件、node provision latency 都送進事故 timeline（可用 Kubernetes event exporter 或 custom metric）。事故 timeline 上看到「HPA 觸發後 3 分鐘 node 才 ready」就能直接判斷「容量補充太慢」而非「應用有 bug」。</li>
</ol>
<h2 id="回退判讀">回退判讀</h2>
<p>擴縮策略變更的回退比應用版本回退簡單——改 HPA / autoscaler 的 config 就好。風險在於回退後的舊策略可能已經跟當前 workload 型態不匹配（workload 成長了、流量特性變了）。穩定做法是回退後立刻進入觀察窗口，確認舊策略在當前流量下仍然有效。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment</a> 看 autoscaling 與部署策略協同。回 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 platform lifecycle contract</a> 看不同 workload 的 lifecycle 差異如何影響擴縮設計。回 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity &amp; cost</a> 看容量規劃的完整框架。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb</a>（原始 URL 已失效，內容基於骨架與通用工程知識擴充）</li>
</ul>
]]></content:encoded></item><item><title>7.C6 Okta：Cross-tenant Impersonation 防禦回寫</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-cross-tenant-impersonation-2023/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-cross-tenant-impersonation-2023/</guid><description>&lt;p>這個案例的核心責任是把跨租戶身份濫用轉成可檢測、可回退的控制流程。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Okta 公開 cross-tenant impersonation 預防與偵測建議，揭示管理員流程與身份策略是關鍵風險點。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>若高權限管理流程與租戶隔離規則未收斂，會形成跨租戶攻擊面。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>收斂高權限管理員權限與適用範圍。&lt;/li>
&lt;li>建立 impersonation 相關事件偵測規則。&lt;/li>
&lt;li>將可疑活動納入 incident triage 快速路由。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection/">Cross-Tenant Impersonation: Prevention and Detection&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把跨租戶身份濫用轉成可檢測、可回退的控制流程。</p>
<h2 id="觀察">觀察</h2>
<p>Okta 公開 cross-tenant impersonation 預防與偵測建議，揭示管理員流程與身份策略是關鍵風險點。</p>
<h2 id="判讀">判讀</h2>
<p>若高權限管理流程與租戶隔離規則未收斂，會形成跨租戶攻擊面。</p>
<h2 id="策略">策略</h2>
<ol>
<li>收斂高權限管理員權限與適用範圍。</li>
<li>建立 impersonation 相關事件偵測規則。</li>
<li>將可疑活動納入 incident triage 快速路由。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2</a> 與 <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.13</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection/">Cross-Tenant Impersonation: Prevention and Detection</a></li>
</ul>
]]></content:encoded></item><item><title>Roblox</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/</guid><description>&lt;p>Roblox 2021 的 73 小時事故是 Consul 流量模式 + long-tail recovery 的教學標竿。事故 post-mortem 詳細揭露根因發現過程、適合作為「為何根因難找」「為何 recovery 比預期慢」的敘事範本。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Consul 流量模式：streaming + 大量 watch 的非預期行為&lt;/li>
&lt;li>根因發現延遲：72 小時內為何無法定位 streaming 是兇手&lt;/li>
&lt;li>Long-tail recovery：服務恢復後為何效能未恢復、cache cold start 影響&lt;/li>
&lt;li>廠商協作：HashiCorp 介入時機、第三方協助的 IR 流程&lt;/li>
&lt;li>Postmortem 公開度：Roblox 罕見的詳細工程敘事&lt;/li>
&lt;/ul>
&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>2021&lt;/td>
 &lt;td>73 小時 outage&lt;/td>
 &lt;td>根因難尋、long-tail recovery、廠商協作&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Roblox 這個案例在講的是長時間事故如何把基礎設施依賴顯性化。讀者先看懂控制面、配置與服務恢復的順序，再把 73 小時這類事件當成 prolonged recovery 的範例。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當核心依賴出現問題時，恢復不只是在某台機器上按下重啟，而是要讓整個服務依賴鏈按順序回來。當事件持續多天時，修復與驗證的節奏要穩定，否則使用者面恢復會反覆抖動。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否說明哪個基礎設施層先恢復&lt;/li>
&lt;li>能否把長尾恢復拆成可驗證的階段&lt;/li>
&lt;li>能否在控制面回穩前避免過早開流量&lt;/li>
&lt;li>能否把 prolonged recovery 的每一步對外說清楚&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Roblox 和 Discord、Heroku 一起讀時，最能看出長連線與多租戶基礎設施的恢復難度。它也能對照 AWS S3，因為兩者都在說明基礎層恢復順序一旦錯了，後面的使用者體感就會反覆抖動。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>73 小時 outage 是長尾恢復與根因難尋的代表案例。&lt;/li>
&lt;li>Return to Service 文件則提供了從事故到結構性改善的完整敘事。&lt;/li>
&lt;li>Consul 的流量模式揭露了意外的 session 壓力。&lt;/li>
&lt;li>廠商協作是 prolonged recovery 的重要組件。&lt;/li>
&lt;li>streaming / watch traffic 讓非預期的控制面壓力浮出來。&lt;/li>
&lt;li>infrastructure efficiency 改善是事故之後的結構性回應。&lt;/li>
&lt;li>streaming / watch traffic 讓非預期的控制面壓力浮出來。&lt;/li>
&lt;li>infrastructure efficiency 改善是事故之後的結構性回應。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://corp.roblox.com/newsroom/2021/10/update-recent-service-outage/">An Update on Our Outage&lt;/a>：Roblox 73 小時 outage 的初始對外說明。&lt;/li>
&lt;li>&lt;a href="https://corp.roblox.com/fr/salledepresse/2022/01/roblox-return-to-service-10-28-10-31-2021">Roblox Return to Service&lt;/a>：完整 return-to-service 與技術復盤。&lt;/li>
&lt;li>&lt;a href="https://corp.roblox.com/de/newsroom/2023/12/making-robloxs-infrastructure-efficient-resilient">How We’re Making Roblox’s Infrastructure More Efficient and Resilient&lt;/a>：後續的結構性改善與 cell 化方向。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Roblox 2021 的 73 小時事故是 Consul 流量模式 + long-tail recovery 的教學標竿。事故 post-mortem 詳細揭露根因發現過程、適合作為「為何根因難找」「為何 recovery 比預期慢」的敘事範本。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Consul 流量模式：streaming + 大量 watch 的非預期行為</li>
<li>根因發現延遲：72 小時內為何無法定位 streaming 是兇手</li>
<li>Long-tail recovery：服務恢復後為何效能未恢復、cache cold start 影響</li>
<li>廠商協作：HashiCorp 介入時機、第三方協助的 IR 流程</li>
<li>Postmortem 公開度：Roblox 罕見的詳細工程敘事</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2021</td>
          <td>73 小時 outage</td>
          <td>根因難尋、long-tail recovery、廠商協作</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>Roblox 這個案例在講的是長時間事故如何把基礎設施依賴顯性化。讀者先看懂控制面、配置與服務恢復的順序，再把 73 小時這類事件當成 prolonged recovery 的範例。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當核心依賴出現問題時，恢復不只是在某台機器上按下重啟，而是要讓整個服務依賴鏈按順序回來。當事件持續多天時，修復與驗證的節奏要穩定，否則使用者面恢復會反覆抖動。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否說明哪個基礎設施層先恢復</li>
<li>能否把長尾恢復拆成可驗證的階段</li>
<li>能否在控制面回穩前避免過早開流量</li>
<li>能否把 prolonged recovery 的每一步對外說清楚</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Roblox 和 Discord、Heroku 一起讀時，最能看出長連線與多租戶基礎設施的恢復難度。它也能對照 AWS S3，因為兩者都在說明基礎層恢復順序一旦錯了，後面的使用者體感就會反覆抖動。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>73 小時 outage 是長尾恢復與根因難尋的代表案例。</li>
<li>Return to Service 文件則提供了從事故到結構性改善的完整敘事。</li>
<li>Consul 的流量模式揭露了意外的 session 壓力。</li>
<li>廠商協作是 prolonged recovery 的重要組件。</li>
<li>streaming / watch traffic 讓非預期的控制面壓力浮出來。</li>
<li>infrastructure efficiency 改善是事故之後的結構性回應。</li>
<li>streaming / watch traffic 讓非預期的控制面壓力浮出來。</li>
<li>infrastructure efficiency 改善是事故之後的結構性回應。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://corp.roblox.com/newsroom/2021/10/update-recent-service-outage/">An Update on Our Outage</a>：Roblox 73 小時 outage 的初始對外說明。</li>
<li><a href="https://corp.roblox.com/fr/salledepresse/2022/01/roblox-return-to-service-10-28-10-31-2021">Roblox Return to Service</a>：完整 return-to-service 與技術復盤。</li>
<li><a href="https://corp.roblox.com/de/newsroom/2023/12/making-robloxs-infrastructure-efficient-resilient">How We’re Making Roblox’s Infrastructure More Efficient and Resilient</a>：後續的結構性改善與 cell 化方向。</li>
</ul>
]]></content:encoded></item><item><title>8.6 Cloudflare：DNS、SSL 與長連線服務</title><link>https://tarrragon.github.io/blog/go/08-case-studies/cloudflare/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/cloudflare/</guid><description>&lt;p>Cloudflare 是理解 Go 高併發價值的最佳案例之一。官方文章提到 Go 被用在 Railgun、DNS infrastructure、SSL、load testing 與多個生產服務中。這些工作共同點都很明顯：大量 I/O、長連線、網路協調與對 latency 的敏感。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/go-at-cloudflare">Go at CloudFlare&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.cloudflare.com/what-weve-been-doing-with-go/">What we&amp;rsquo;ve been doing with Go&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.cloudflare.com/go-hack-nights/">Go Hack Nights at Cloudflare&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>goroutine 與 channel 很適合處理大量網路事件。&lt;/li>
&lt;li>Go 在低延遲連線服務中特別自然。&lt;/li>
&lt;li>服務邊界、節奏控制與生命週期管理是關鍵，不只是 raw throughput。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/cloudflare/cloudflared">cloudflare/cloudflared&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cloudflare/cloudflare-go">cloudflare/cloudflare-go&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這兩個 repo 很適合拿來看長連線代理、API client、context 使用與依賴組裝。&lt;/p></description><content:encoded><![CDATA[<p>Cloudflare 是理解 Go 高併發價值的最佳案例之一。官方文章提到 Go 被用在 Railgun、DNS infrastructure、SSL、load testing 與多個生產服務中。這些工作共同點都很明顯：大量 I/O、長連線、網路協調與對 latency 的敏感。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://blog.cloudflare.com/go-at-cloudflare">Go at CloudFlare</a></li>
<li><a href="https://blog.cloudflare.com/what-weve-been-doing-with-go/">What we&rsquo;ve been doing with Go</a></li>
<li><a href="https://blog.cloudflare.com/go-hack-nights/">Go Hack Nights at Cloudflare</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>goroutine 與 channel 很適合處理大量網路事件。</li>
<li>Go 在低延遲連線服務中特別自然。</li>
<li>服務邊界、節奏控制與生命週期管理是關鍵，不只是 raw throughput。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/cloudflare/cloudflared">cloudflare/cloudflared</a></li>
<li><a href="https://github.com/cloudflare/cloudflare-go">cloudflare/cloudflare-go</a></li>
</ul>
<p>這兩個 repo 很適合拿來看長連線代理、API client、context 使用與依賴組裝。</p>
]]></content:encoded></item><item><title>9.C7 Lyft：100+ 微服務在 8 倍峰值下的 Auto Scaling</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/</guid><description>&lt;p>這個案例的核心責任是說明「微服務架構在事件型峰值下的容量治理」。共乘服務的負載形狀獨特 — 平日早晚通勤雙峰、週末晚間爆量、特殊事件（演唱會、球賽結束、機場）瞬間爆量、每個城市跟每個時段都不同。100+ 個微服務各自有不同的峰值時段、需要獨立擴容策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Lyft 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft case study&lt;/a>）：&lt;/p>
&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>8x 平日基線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數&lt;/td>
 &lt;td>100+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>月均搭乘&lt;/td>
 &lt;td>1400 萬 / 月&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務城市&lt;/td>
 &lt;td>200+&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon DynamoDB（搭乘追蹤、GPS 座標）、Amazon Redshift（客戶洞察）、Amazon Kinesis（即時事件串流）、AWS Auto Scaling、Amazon EC2 Container Registry。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Lyft 的工程做法揭露三個微服務容量治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>微服務不是「全部 8x」、是「特定服務 8x」&lt;/strong>：8x 是 &lt;em>某些核心服務&lt;/em> 在週末爆量時刻的擴容比、不是 100 個服務全部 8x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 必須先做「哪個服務是熱點」的層次定位。&lt;/li>
&lt;li>&lt;strong>微服務粒度 = 擴容粒度&lt;/strong>：把 ride matching、payment、driver tracking、notification 切成獨立服務、每個服務的 autoscaling policy 可以獨立設計。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的服務邊界。&lt;/li>
&lt;li>&lt;strong>GPS 座標寫入 DynamoDB 是高頻 sustained workload&lt;/strong>：每個 driver 每秒寫 1-2 次位置、200+ 城市 × 每個城市數萬司機 = 巨量持續寫入、跟峰值無關。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 KV 高吞吐設計同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「8x 峰值」是 &lt;em>峰值倍數&lt;/em>、不是 &lt;em>尖峰持續時間&lt;/em>。週末晚間的尖峰可能持續 3-4 小時、機場特殊事件可能持續 30 分鐘、演唱會結束可能只有 10 分鐘瞬間。容量策略要按持續時間區分。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>微服務粒度切到「同性質擴容單位」&lt;/strong>：同步 vs async、stateful vs stateless、CPU-bound vs I/O-bound 不該混在同一服務、否則擴容邏輯互相衝突。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 service decomposition。&lt;/li>
&lt;li>&lt;strong>預測式 + 反應式擴容混用&lt;/strong>：可預測（早晚通勤）用 scheduled scaling、不可預測（演唱會散場）用 reactive autoscaling、兩者組合。&lt;/li>
&lt;li>&lt;strong>GPS 類持續寫入適合 KV / time-series store&lt;/strong>：不適合放 OLTP DB、會佔用 transaction 資源。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 storage choice。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE + HPA / VPA / Karpenter、Azure AKS + KEDA、自建 Kubernetes + Cluster Autoscaler 都可以實作對等架構。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想做微服務容量治理 → &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>&lt;/li>
&lt;li>想規劃事件型峰值 → &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech&lt;/a>&lt;/li>
&lt;li>想設計高頻 sustained workload → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft Case Study&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「微服務架構在事件型峰值下的容量治理」。共乘服務的負載形狀獨特 — 平日早晚通勤雙峰、週末晚間爆量、特殊事件（演唱會、球賽結束、機場）瞬間爆量、每個城市跟每個時段都不同。100+ 個微服務各自有不同的峰值時段、需要獨立擴容策略。</p>
<h2 id="觀察">觀察</h2>
<p>Lyft 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>峰值倍數</td>
          <td>8x 平日基線</td>
      </tr>
      <tr>
          <td>微服務數</td>
          <td>100+ 個</td>
      </tr>
      <tr>
          <td>月均搭乘</td>
          <td>1400 萬 / 月</td>
      </tr>
      <tr>
          <td>服務城市</td>
          <td>200+</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon DynamoDB（搭乘追蹤、GPS 座標）、Amazon Redshift（客戶洞察）、Amazon Kinesis（即時事件串流）、AWS Auto Scaling、Amazon EC2 Container Registry。</p>
<h2 id="判讀">判讀</h2>
<p>Lyft 的工程做法揭露三個微服務容量治理重點。</p>
<ol>
<li><strong>微服務不是「全部 8x」、是「特定服務 8x」</strong>：8x 是 <em>某些核心服務</em> 在週末爆量時刻的擴容比、不是 100 個服務全部 8x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 必須先做「哪個服務是熱點」的層次定位。</li>
<li><strong>微服務粒度 = 擴容粒度</strong>：把 ride matching、payment、driver tracking、notification 切成獨立服務、每個服務的 autoscaling policy 可以獨立設計。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 跟 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的服務邊界。</li>
<li><strong>GPS 座標寫入 DynamoDB 是高頻 sustained workload</strong>：每個 driver 每秒寫 1-2 次位置、200+ 城市 × 每個城市數萬司機 = 巨量持續寫入、跟峰值無關。對應 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 的 KV 高吞吐設計同類。</li>
</ol>
<p>需要警惕：「8x 峰值」是 <em>峰值倍數</em>、不是 <em>尖峰持續時間</em>。週末晚間的尖峰可能持續 3-4 小時、機場特殊事件可能持續 30 分鐘、演唱會結束可能只有 10 分鐘瞬間。容量策略要按持續時間區分。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>微服務粒度切到「同性質擴容單位」</strong>：同步 vs async、stateful vs stateless、CPU-bound vs I/O-bound 不該混在同一服務、否則擴容邏輯互相衝突。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition。</li>
<li><strong>預測式 + 反應式擴容混用</strong>：可預測（早晚通勤）用 scheduled scaling、不可預測（演唱會散場）用 reactive autoscaling、兩者組合。</li>
<li><strong>GPS 類持續寫入適合 KV / time-series store</strong>：不適合放 OLTP DB、會佔用 transaction 資源。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 storage choice。</li>
</ol>
<p>跨平台等效：GCP GKE + HPA / VPA / Karpenter、Azure AKS + KEDA、自建 Kubernetes + Cluster Autoscaler 都可以實作對等架構。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做微服務容量治理 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想規劃事件型峰值 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a></li>
<li>想設計高頻 sustained workload → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft Case Study</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a></li>
</ul>
]]></content:encoded></item><item><title>2.C7 Cloudflare：Cache Reserve 分層儲存快取</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/cloudflare-cache-reserve-tiered-storage/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/cloudflare-cache-reserve-tiered-storage/</guid><description>&lt;p>這個案例的核心責任是把快取從短期命中策略擴展到長期容量策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Cloudflare Cache Reserve 透過分層儲存延長快取可用性，降低 origin 回源成本。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當熱門資料長尾明顯，僅靠 edge cache 會有命中率上限，需引入分層儲存。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>定義 edge 與 reserve 的資料分層規則。&lt;/li>
&lt;li>把回源成本納入快取策略評估。&lt;/li>
&lt;li>監控命中率、延遲與儲存成本三者平衡。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/introducing-cache-reserve/">Cloudflare Cache Reserve&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把快取從短期命中策略擴展到長期容量策略。</p>
<h2 id="觀察">觀察</h2>
<p>Cloudflare Cache Reserve 透過分層儲存延長快取可用性，降低 origin 回源成本。</p>
<h2 id="判讀">判讀</h2>
<p>當熱門資料長尾明顯，僅靠 edge cache 會有命中率上限，需引入分層儲存。</p>
<h2 id="策略">策略</h2>
<ol>
<li>定義 edge 與 reserve 的資料分層規則。</li>
<li>把回源成本納入快取策略評估。</li>
<li>監控命中率、延遲與儲存成本三者平衡。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/ttl-eviction/" data-link-title="2.3 TTL 與 eviction" data-link-desc="整理過期策略、容量控制與熱點資料">2.3</a> 與 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/introducing-cache-reserve/">Cloudflare Cache Reserve</a></li>
</ul>
]]></content:encoded></item><item><title>3.C7 LinkedIn：Kafka 自動修復治理</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/</guid><description>&lt;p>這個案例的核心責任是把 queue 可靠性從人力值班轉成自動化機制。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LinkedIn 在 Kafka 維運中導入自動化治理，降低人工介入與恢復時間波動。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當叢集規模超過人力可及範圍，自動修復與治理工具會成為必要能力。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>明確定義可自動修復的故障類型。&lt;/li>
&lt;li>將自動修復與人工升級條件分離。&lt;/li>
&lt;li>把修復過程納入可觀測證據鏈。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.linkedin.com/blog">Automating Kafka Self-Healing at LinkedIn&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 queue 可靠性從人力值班轉成自動化機制。</p>
<h2 id="觀察">觀察</h2>
<p>LinkedIn 在 Kafka 維運中導入自動化治理，降低人工介入與恢復時間波動。</p>
<h2 id="判讀">判讀</h2>
<p>當叢集規模超過人力可及範圍，自動修復與治理工具會成為必要能力。</p>
<h2 id="策略">策略</h2>
<ol>
<li>明確定義可自動修復的故障類型。</li>
<li>將自動修復與人工升級條件分離。</li>
<li>把修復過程納入可觀測證據鏈。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2</a> 與 <a href="/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/blog">Automating Kafka Self-Healing at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>4.C7 Datadog：OTel 相容遷移實務</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/datadog-otel-migration-practice/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/datadog-otel-migration-practice/</guid><description>&lt;p>這個案例的核心責任是把 observability 遷移做成可逐步替換的技術路線。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Datadog 與 OTel 生態整合的做法，顯示團隊可在不一次重寫下逐步切換採集管線。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>觀測遷移的主要風險是資料語意漂移與管線雙軌期成本，而非單一 agent 安裝。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立雙軌採集的對照驗證。&lt;/li>
&lt;li>把 schema 與 sampling 政策版本化。&lt;/li>
&lt;li>用品質指標決定何時關閉舊管線。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.datadoghq.com/blog/instrument-python-apps-with-datadog-and-opentelemetry/">Datadog and OpenTelemetry&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 observability 遷移做成可逐步替換的技術路線。</p>
<h2 id="觀察">觀察</h2>
<p>Datadog 與 OTel 生態整合的做法，顯示團隊可在不一次重寫下逐步切換採集管線。</p>
<h2 id="判讀">判讀</h2>
<p>觀測遷移的主要風險是資料語意漂移與管線雙軌期成本，而非單一 agent 安裝。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立雙軌採集的對照驗證。</li>
<li>把 schema 與 sampling 政策版本化。</li>
<li>用品質指標決定何時關閉舊管線。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a> 與 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.datadoghq.com/blog/instrument-python-apps-with-datadog-and-opentelemetry/">Datadog and OpenTelemetry</a></li>
</ul>
]]></content:encoded></item><item><title>5.C7 Airbnb：Istio 升級治理</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-istio-upgrade-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-istio-upgrade-governance/</guid><description>&lt;p>這個案例的核心責任是把平台元件升級從一次性作業轉成可重播流程。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 在數十個 Kubernetes 叢集、數萬個 pod、數千個 VM 的規模下持續升級 Istio service mesh，峰值流量達數千萬 QPS。團隊累計完成 14 次成功的 Istio 升級。&lt;/p>
&lt;p>升級的核心挑戰是規模帶來的協同成本：無法逐一通知每個 workload team 進行升級配合，也無法同時監控所有 workload 的升級狀態。升級策略必須對 workload team 透明——workload 不需要改程式碼或調配置就能完成 proxy 版本切換。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>基礎平台元件升級若缺乏分批治理，會形成全域風險放大器。Istio 升級的影響面覆蓋所有跑 sidecar 的服務——一次壞的升級可以讓整個叢集的服務間通訊中斷。這個風險決定了升級策略必須是 canary 模式（小比例先行），而且 canary 的粒度要夠細（namespace 或 workload 級別），才能在問題擴大前攔截。&lt;/p>
&lt;p>另一個判讀是升級流程本身要版本化。第一次升級靠資深工程師手動操作可以成功，但這個知識留在個人經驗裡。第二次升級換了人就可能踩到不同的坑。把升級流程固定成可重播的 spec（升級計畫 → 執行 → 驗證 → 確認/回退），讓升級從「英雄行為」變成「例行操作」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Canary upgrade model（兩版本並存）&lt;/strong>：採用 Istio 的 canary upgrade 機制，同時跑兩個版本的 Istiod。新版本的 sidecar proxy 跟對應版本的 control plane 配置一起原子部署，避免跨版本相容性問題。透過 revision label 決定每個 namespace 使用哪個版本的 Istiod。&lt;/li>
&lt;li>&lt;strong>自建工具解耦基礎設施更新與 workload 部署&lt;/strong>：團隊開發了 Krispr（mutation framework），在 CI 階段注入 Istio revision label，並在 admission 階段對超過兩週未部署的 pod 重新注入最新 label。這讓 workload 在正常部署流程中自動完成 proxy 升級，不需要額外操作。&lt;/li>
&lt;li>&lt;strong>rollouts.yml 定義升級批次與比例&lt;/strong>：用 spec 檔定義每個環境（staging / production）、每個 namespace pattern 的版本分佈（例如 staging 75% 舊版 / 25% 新版）。比例可以逐步調整——先 5% → 25% → 50% → 100%。每個批次有明確的觀測窗口與停損條件。&lt;/li>
&lt;li>&lt;strong>VM 升級用 mxrc controller&lt;/strong>：Kubernetes 外的 VM workload 用 mxrc controller 根據 rollouts.yml 更新 tag，遵守健康狀態檢查與可用性門檻。VM 的升級通常在兩週內透過自然輪替完成。&lt;/li>
&lt;li>&lt;strong>升級事件進 incident timeline&lt;/strong>：升級期間的短暫錯誤（proxy 重連、配置同步延遲）在事故 timeline 上標記為升級事件，避免被誤判成獨立事故。升級的決策紀錄用 incident decision log 格式，讓下次升級可以回溯上次的判斷依據。&lt;/li>
&lt;/ol>
&lt;h2 id="升級節奏的收斂">升級節奏的收斂&lt;/h2>
&lt;p>14 次升級的經驗讓升級流程逐步收斂。多數 workload 在正常 deployment 時自動完成 proxy 升級（因為 Krispr 在 admission 階段注入最新 revision）。沒有 regular deployment 的 workload 在四週內透過自然 pod cycling（node 維護、HPA 調整）完成升級。這個四週窗口是可接受的——超過四週未部署的 workload 通常也是低變動、低風險的。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>Istio 升級的回退是把 revision label 切回舊版本、讓 pod 在下次 restart 時重新注入舊版 sidecar。回退的風險在於回退期間新舊 proxy 混跑，traffic policy 可能不完全一致。穩定做法是先在小範圍驗證回退行為（一個 namespace），確認 traffic policy 一致性後再擴大回退範圍。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment&lt;/a> 看 rollout 節奏與 probe 設計。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#%e5%b9%b3%e5%8f%b0%e5%85%83%e4%bb%b6%e5%8d%87%e7%b4%9a%e7%9a%84%e5%8f%af%e9%87%8d%e6%92%ad%e6%b5%81%e7%a8%8b" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 平台元件升級的可重播流程&lt;/a> 看通用升級框架。回 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&amp;#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.6 IC handoff&lt;/a> 看升級期事故的指揮交接。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是把平台元件升級從一次性作業轉成可重播流程。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 在數十個 Kubernetes 叢集、數萬個 pod、數千個 VM 的規模下持續升級 Istio service mesh，峰值流量達數千萬 QPS。團隊累計完成 14 次成功的 Istio 升級。</p>
<p>升級的核心挑戰是規模帶來的協同成本：無法逐一通知每個 workload team 進行升級配合，也無法同時監控所有 workload 的升級狀態。升級策略必須對 workload team 透明——workload 不需要改程式碼或調配置就能完成 proxy 版本切換。</p>
<h2 id="判讀">判讀</h2>
<p>基礎平台元件升級若缺乏分批治理，會形成全域風險放大器。Istio 升級的影響面覆蓋所有跑 sidecar 的服務——一次壞的升級可以讓整個叢集的服務間通訊中斷。這個風險決定了升級策略必須是 canary 模式（小比例先行），而且 canary 的粒度要夠細（namespace 或 workload 級別），才能在問題擴大前攔截。</p>
<p>另一個判讀是升級流程本身要版本化。第一次升級靠資深工程師手動操作可以成功，但這個知識留在個人經驗裡。第二次升級換了人就可能踩到不同的坑。把升級流程固定成可重播的 spec（升級計畫 → 執行 → 驗證 → 確認/回退），讓升級從「英雄行為」變成「例行操作」。</p>
<h2 id="策略">策略</h2>
<ol>
<li><strong>Canary upgrade model（兩版本並存）</strong>：採用 Istio 的 canary upgrade 機制，同時跑兩個版本的 Istiod。新版本的 sidecar proxy 跟對應版本的 control plane 配置一起原子部署，避免跨版本相容性問題。透過 revision label 決定每個 namespace 使用哪個版本的 Istiod。</li>
<li><strong>自建工具解耦基礎設施更新與 workload 部署</strong>：團隊開發了 Krispr（mutation framework），在 CI 階段注入 Istio revision label，並在 admission 階段對超過兩週未部署的 pod 重新注入最新 label。這讓 workload 在正常部署流程中自動完成 proxy 升級，不需要額外操作。</li>
<li><strong>rollouts.yml 定義升級批次與比例</strong>：用 spec 檔定義每個環境（staging / production）、每個 namespace pattern 的版本分佈（例如 staging 75% 舊版 / 25% 新版）。比例可以逐步調整——先 5% → 25% → 50% → 100%。每個批次有明確的觀測窗口與停損條件。</li>
<li><strong>VM 升級用 mxrc controller</strong>：Kubernetes 外的 VM workload 用 mxrc controller 根據 rollouts.yml 更新 tag，遵守健康狀態檢查與可用性門檻。VM 的升級通常在兩週內透過自然輪替完成。</li>
<li><strong>升級事件進 incident timeline</strong>：升級期間的短暫錯誤（proxy 重連、配置同步延遲）在事故 timeline 上標記為升級事件，避免被誤判成獨立事故。升級的決策紀錄用 incident decision log 格式，讓下次升級可以回溯上次的判斷依據。</li>
</ol>
<h2 id="升級節奏的收斂">升級節奏的收斂</h2>
<p>14 次升級的經驗讓升級流程逐步收斂。多數 workload 在正常 deployment 時自動完成 proxy 升級（因為 Krispr 在 admission 階段注入最新 revision）。沒有 regular deployment 的 workload 在四週內透過自然 pod cycling（node 維護、HPA 調整）完成升級。這個四週窗口是可接受的——超過四週未部署的 workload 通常也是低變動、低風險的。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>Istio 升級的回退是把 revision label 切回舊版本、讓 pod 在下次 restart 時重新注入舊版 sidecar。回退的風險在於回退期間新舊 proxy 混跑，traffic policy 可能不完全一致。穩定做法是先在小範圍驗證回退行為（一個 namespace），確認 traffic policy 一致性後再擴大回退範圍。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/kubernetes-deployment/" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 kubernetes deployment</a> 看 rollout 節奏與 probe 設計。回 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#%e5%b9%b3%e5%8f%b0%e5%85%83%e4%bb%b6%e5%8d%87%e7%b4%9a%e7%9a%84%e5%8f%af%e9%87%8d%e6%92%ad%e6%b5%81%e7%a8%8b" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 平台元件升級的可重播流程</a> 看通用升級框架。回 <a href="/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.6 IC handoff</a> 看升級期事故的指揮交接。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://airbnb.tech/infrastructure/seamless-istio-upgrades-at-scale/">Seamless Istio Upgrades at Scale</a></li>
</ul>
]]></content:encoded></item><item><title>7.C7 Okta：BYO Telephony 的身份安全責任轉換</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-byo-telephony-security-shift/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-byo-telephony-security-shift/</guid><description>&lt;p>這個案例的核心責任是說明身份安全控制也會出現供應鏈責任重分配。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Okta 推動 BYO telephony，將 SMS/voice MFA 的供應商控制責任轉給客戶側治理。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這類轉換是信任邊界與責任邊界變更，需要同步更新風險模型，單純當功能變更處理會漏掉安全面。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>明確定義 telephony provider 的安全要求。&lt;/li>
&lt;li>把供應商變更納入身份風險評估節奏。&lt;/li>
&lt;li>建立跨供應商故障與濫用應變流程。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.10&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sec.okta.com/articles/2023/08/byo-telephony-and-future-sms-okta/">BYO Telephony and the future of SMS at Okta&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明身份安全控制也會出現供應鏈責任重分配。</p>
<h2 id="觀察">觀察</h2>
<p>Okta 推動 BYO telephony，將 SMS/voice MFA 的供應商控制責任轉給客戶側治理。</p>
<h2 id="判讀">判讀</h2>
<p>這類轉換是信任邊界與責任邊界變更，需要同步更新風險模型，單純當功能變更處理會漏掉安全面。</p>
<h2 id="策略">策略</h2>
<ol>
<li>明確定義 telephony provider 的安全要求。</li>
<li>把供應商變更納入身份風險評估節奏。</li>
<li>建立跨供應商故障與濫用應變流程。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.10</a> 與 <a href="/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sec.okta.com/articles/2023/08/byo-telephony-and-future-sms-okta/">BYO Telephony and the future of SMS at Okta</a></li>
</ul>
]]></content:encoded></item><item><title>Fastly</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/</guid><description>&lt;p>Fastly 2021-06 的全球分鐘級配置 push 事故是 edge platform 的客戶配置觸發供應商 bug 的教學標竿。事件揭露了「客戶觸發供應商 bug」這類 IR 議題的特殊性、跟 Cloudflare 配置事故有對照價值。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>客戶配置觸發供應商 bug：誰負責、誰補償、誰公開&lt;/li>
&lt;li>全球 edge 分鐘級擴散：為何 edge platform 出事規模特別大&lt;/li>
&lt;li>Recovery 機制：客戶配置回退 vs 供應商 hotfix 的取捨&lt;/li>
&lt;li>通訊責任：上下游服務（Reddit、Amazon、政府網站）受影響時的 status 揭露&lt;/li>
&lt;/ul>
&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>2021-06&lt;/td>
 &lt;td>全球分鐘級配置 push 失效&lt;/td>
 &lt;td>客戶配置觸發、edge platform blast radius&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例清單">案例清單&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="建議閱讀順序">建議閱讀順序&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Fastly 這個案例在講的是一個小型配置錯誤如何透過 edge 網路快速放大。讀者先看懂配置驗證、全球推送與回滾的責任，再把這類事故視為 control-plane 失誤，而不是單點節點故障。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當壞配置進入全球推送鏈時，真正關鍵的步驟是能否快速阻斷傳播，事後修補只能限縮損失範圍。當回復開始時，還要同時確認快取、路由與客戶流量是否已回到預期狀態。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否在推送前把配置驗證到足夠高的信心&lt;/li>
&lt;li>能否即時看見錯誤配置的擴散跡象&lt;/li>
&lt;li>能否把 rollback 做成高優先序動作&lt;/li>
&lt;li>能否把 global propagation 與客戶影響對齊&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Fastly 和 Cloudflare 是最接近的一組對照頁，兩者都在講 edge 網路上的配置擴散。Fastly 更適合用來看「客戶配置觸發供應商 bug」這個特殊模式，和 AWS S3 的區域控制面事故放在一起時，會更容易分辨不同層級的 blast radius。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2021-06 全球分鐘級配置 push 失效是最典型的 edge propagation 樣本。&lt;/li>
&lt;li>這類事故強調回滾速度與配置驗證必須先於全球擴散。&lt;/li>
&lt;li>客戶配置觸發供應商 bug 是 edge 平台最難處理的模式之一。&lt;/li>
&lt;li>Fastly 的樣本能和 Cloudflare、AWS S3 一起看 blast radius。&lt;/li>
&lt;li>CDN 邊緣層的壓力會把一個小錯誤迅速推成全球事件。&lt;/li>
&lt;li>rollback 與 status 通訊必須同步，否則客戶只會看到更長的黑箱。&lt;/li>
&lt;li>deploy tool misconfiguration 讓工具本身變成事故起點。&lt;/li>
&lt;li>edge runtime 的錯誤驗證不充分時，影響會直接落到全球流量。&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.fastly.com/blog/summary-of-june-8-outage">Summary of June 8 outage&lt;/a>：Fastly 2021-06 全球 outage 的官方回顧。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Fastly 2021-06 的全球分鐘級配置 push 事故是 edge platform 的客戶配置觸發供應商 bug 的教學標竿。事件揭露了「客戶觸發供應商 bug」這類 IR 議題的特殊性、跟 Cloudflare 配置事故有對照價值。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>客戶配置觸發供應商 bug：誰負責、誰補償、誰公開</li>
<li>全球 edge 分鐘級擴散：為何 edge platform 出事規模特別大</li>
<li>Recovery 機制：客戶配置回退 vs 供應商 hotfix 的取捨</li>
<li>通訊責任：上下游服務（Reddit、Amazon、政府網站）受影響時的 status 揭露</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2021-06</td>
          <td>全球分鐘級配置 push 失效</td>
          <td>客戶配置觸發、edge platform blast radius</td>
      </tr>
  </tbody>
</table>
<h2 id="案例清單">案例清單</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage</a></li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li><a href="/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage</a></li>
</ol>
<h2 id="案例定位">案例定位</h2>
<p>Fastly 這個案例在講的是一個小型配置錯誤如何透過 edge 網路快速放大。讀者先看懂配置驗證、全球推送與回滾的責任，再把這類事故視為 control-plane 失誤，而不是單點節點故障。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當壞配置進入全球推送鏈時，真正關鍵的步驟是能否快速阻斷傳播，事後修補只能限縮損失範圍。當回復開始時，還要同時確認快取、路由與客戶流量是否已回到預期狀態。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否在推送前把配置驗證到足夠高的信心</li>
<li>能否即時看見錯誤配置的擴散跡象</li>
<li>能否把 rollback 做成高優先序動作</li>
<li>能否把 global propagation 與客戶影響對齊</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Fastly 和 Cloudflare 是最接近的一組對照頁，兩者都在講 edge 網路上的配置擴散。Fastly 更適合用來看「客戶配置觸發供應商 bug」這個特殊模式，和 AWS S3 的區域控制面事故放在一起時，會更容易分辨不同層級的 blast radius。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2021-06 全球分鐘級配置 push 失效是最典型的 edge propagation 樣本。</li>
<li>這類事故強調回滾速度與配置驗證必須先於全球擴散。</li>
<li>客戶配置觸發供應商 bug 是 edge 平台最難處理的模式之一。</li>
<li>Fastly 的樣本能和 Cloudflare、AWS S3 一起看 blast radius。</li>
<li>CDN 邊緣層的壓力會把一個小錯誤迅速推成全球事件。</li>
<li>rollback 與 status 通訊必須同步，否則客戶只會看到更長的黑箱。</li>
<li>deploy tool misconfiguration 讓工具本身變成事故起點。</li>
<li>edge runtime 的錯誤驗證不充分時，影響會直接落到全球流量。</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.fastly.com/blog/summary-of-june-8-outage">Summary of June 8 outage</a>：Fastly 2021-06 全球 outage 的官方回顧。</li>
</ul>
]]></content:encoded></item><item><title>8.7 Cockroach Labs：分散式 SQL 資料庫</title><link>https://tarrragon.github.io/blog/go/08-case-studies/cockroach-labs/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/cockroach-labs/</guid><description>&lt;p>Cockroach Labs 的案例適合放在 Go 教材裡，因為它把 Go 的工程價值推到很高的門檻：分散式 SQL、交易一致性、可水平擴展、容錯與長期可維護。官方案例直接提到，Go 的 performance、garbage collection 與低入門門檻，是 CockroachDB 的重要選擇原因。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.cockroachlabs.com/blog/why-go-was-the-right-choice-for-cockroachdb/">Why Go was the right choice for CockroachDB&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cockroachlabs.com/docs/stable/why-cockroachdb">Why CockroachDB?&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 不只適合 API，也適合超大型資料系統。&lt;/li>
&lt;li>大型系統裡，語言的可讀性與團隊進入門檻很重要。&lt;/li>
&lt;li>Go 在複雜系統中的優勢，常常是讓工程複雜度可控。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/cockroachdb/cockroach">cockroachdb/cockroach&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cockroachdb/cockroach-go">cockroachdb/cockroach-go&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這是本模組最值得讀的 repo 之一。你可以對照第七模組的 package 邊界、接口設計與 composition root，理解大型 Go 系統如何組織。&lt;/p></description><content:encoded><![CDATA[<p>Cockroach Labs 的案例適合放在 Go 教材裡，因為它把 Go 的工程價值推到很高的門檻：分散式 SQL、交易一致性、可水平擴展、容錯與長期可維護。官方案例直接提到，Go 的 performance、garbage collection 與低入門門檻，是 CockroachDB 的重要選擇原因。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://www.cockroachlabs.com/blog/why-go-was-the-right-choice-for-cockroachdb/">Why Go was the right choice for CockroachDB</a></li>
<li><a href="https://www.cockroachlabs.com/docs/stable/why-cockroachdb">Why CockroachDB?</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 不只適合 API，也適合超大型資料系統。</li>
<li>大型系統裡，語言的可讀性與團隊進入門檻很重要。</li>
<li>Go 在複雜系統中的優勢，常常是讓工程複雜度可控。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/cockroachdb/cockroach">cockroachdb/cockroach</a></li>
<li><a href="https://github.com/cockroachdb/cockroach-go">cockroachdb/cockroach-go</a></li>
</ul>
<p>這是本模組最值得讀的 repo 之一。你可以對照第七模組的 package 邊界、接口設計與 composition root，理解大型 Go 系統如何組織。</p>
]]></content:encoded></item><item><title>9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/</guid><description>&lt;p>這個案例的核心責任是說明「surge load」（突發遠超預期）跟 event-peak（事件型可預測峰值）的差異。Pokémon GO 在 2016-07 上線時、實際流量達到原始容量規劃目標的 50 倍 — 根因是 &lt;em>根本沒人能預測這個產品會這麼紅&lt;/em>、峰值規劃方法論本身沒有失敗。這類負載對容量設計的要求跟其他案例本質不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Niantic Pokémon GO 在 GCP 上的關鍵敘述（引自 &lt;a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud&lt;/a>）：&lt;/p>
&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>達到原始 target 的 50 倍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>應用層&lt;/td>
 &lt;td>Google Container Engine (GKE)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容器編排&lt;/td>
 &lt;td>Kubernetes（planetary-scale 設計）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量支援&lt;/td>
 &lt;td>Google CRE 即時擴容&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「Niantic chose GKE for its ability to orchestrate container clusters at planetary-scale」「Google CRE seamlessly provisioned extra capacity on behalf of Niantic to stay ahead of their record-setting growth」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最重要的判讀是「surge load 跟可預測峰值是不同問題」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>50x surge 沒辦法事前規劃&lt;/strong>：任何合理的 capacity planning 都不會預留 50x headroom — 那會讓平日成本爆炸。surge 的工程做法不是「事前撐住」、是「事中快速補上」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理模組&lt;/a> 的事件管理。&lt;/li>
&lt;li>&lt;strong>CRE 不是技術、是 vendor 關係&lt;/strong>：Google Customer Reliability Engineering 是 GCP 提供給戰略客戶的 24/7 工程支援團隊。能即時為 Niantic 補容量靠的是 &lt;em>人 + 流程 + 工具&lt;/em> 的組合、不是純技術。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 操作控制服務選型&lt;/a> 的廠商支援能力評估。&lt;/li>
&lt;li>&lt;strong>Kubernetes 是 surge 的前置條件&lt;/strong>：如果 Niantic 用 VM-based 架構、即使 CRE 想補容量也來不及 boot up。Container orchestrator 把 provisioning 時間從分鐘級降到秒級、才讓 surge 反應變得可能。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 platform 選型。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「Google CRE 即時補容量」這種敘述對中小客戶不適用。一般客戶在 surge 下能依賴的是 &lt;em>自己的 autoscaler&lt;/em>、不是 vendor 工程師。設計 surge 對應策略時要假設「沒有 vendor 救援」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>接受 surge 不可避免、設計快速 onboard 流程&lt;/strong>：核心問題不是「會不會 surge」、是「surge 之後 24 小時內能不能撐住」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">08.8 incident communication&lt;/a>。&lt;/li>
&lt;li>&lt;strong>降級機制作為 surge 救命稻草&lt;/strong>：當容量不足時、優先保住核心功能、暫時關閉非核心。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02.3 cache stampede&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access&lt;/a> 的降級設計。&lt;/li>
&lt;li>&lt;strong>預先談好 vendor 緊急支援條款&lt;/strong>：戰略服務在簽約時就要談好 surge 期間的容量配額、限流豁免、CRE / TAM 支援、不要等出事才談。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 vendor relationship 設計。&lt;/li>
&lt;li>&lt;strong>container-first 是 surge 反應的前置&lt;/strong>：VM-based 架構在 surge 下擴容速度比 container 慢一個量級、會直接成為 bottleneck。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS Enterprise Support + TAM、Azure Premier Support + CSAM 都有對等服務、但能即時動用工程師補容量的程度跟客戶等級綁定。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「surge load」（突發遠超預期）跟 event-peak（事件型可預測峰值）的差異。Pokémon GO 在 2016-07 上線時、實際流量達到原始容量規劃目標的 50 倍 — 根因是 <em>根本沒人能預測這個產品會這麼紅</em>、峰值規劃方法論本身沒有失敗。這類負載對容量設計的要求跟其他案例本質不同。</p>
<h2 id="觀察">觀察</h2>
<p>Niantic Pokémon GO 在 GCP 上的關鍵敘述（引自 <a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實際流量</td>
          <td>達到原始 target 的 50 倍</td>
      </tr>
      <tr>
          <td>應用層</td>
          <td>Google Container Engine (GKE)</td>
      </tr>
      <tr>
          <td>容器編排</td>
          <td>Kubernetes（planetary-scale 設計）</td>
      </tr>
      <tr>
          <td>容量支援</td>
          <td>Google CRE 即時擴容</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「Niantic chose GKE for its ability to orchestrate container clusters at planetary-scale」「Google CRE seamlessly provisioned extra capacity on behalf of Niantic to stay ahead of their record-setting growth」。</p>
<h2 id="判讀">判讀</h2>
<p>這個案例最重要的判讀是「surge load 跟可預測峰值是不同問題」。</p>
<ol>
<li><strong>50x surge 沒辦法事前規劃</strong>：任何合理的 capacity planning 都不會預留 50x headroom — 那會讓平日成本爆炸。surge 的工程做法不是「事前撐住」、是「事中快速補上」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理模組</a> 的事件管理。</li>
<li><strong>CRE 不是技術、是 vendor 關係</strong>：Google Customer Reliability Engineering 是 GCP 提供給戰略客戶的 24/7 工程支援團隊。能即時為 Niantic 補容量靠的是 <em>人 + 流程 + 工具</em> 的組合、不是純技術。對應 <a href="/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 操作控制服務選型</a> 的廠商支援能力評估。</li>
<li><strong>Kubernetes 是 surge 的前置條件</strong>：如果 Niantic 用 VM-based 架構、即使 CRE 想補容量也來不及 boot up。Container orchestrator 把 provisioning 時間從分鐘級降到秒級、才讓 surge 反應變得可能。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 platform 選型。</li>
</ol>
<p>需要警惕：「Google CRE 即時補容量」這種敘述對中小客戶不適用。一般客戶在 surge 下能依賴的是 <em>自己的 autoscaler</em>、不是 vendor 工程師。設計 surge 對應策略時要假設「沒有 vendor 救援」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>接受 surge 不可避免、設計快速 onboard 流程</strong>：核心問題不是「會不會 surge」、是「surge 之後 24 小時內能不能撐住」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">08.8 incident communication</a>。</li>
<li><strong>降級機制作為 surge 救命稻草</strong>：當容量不足時、優先保住核心功能、暫時關閉非核心。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02.3 cache stampede</a> 跟 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access</a> 的降級設計。</li>
<li><strong>預先談好 vendor 緊急支援條款</strong>：戰略服務在簽約時就要談好 surge 期間的容量配額、限流豁免、CRE / TAM 支援、不要等出事才談。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor relationship 設計。</li>
<li><strong>container-first 是 surge 反應的前置</strong>：VM-based 架構在 surge 下擴容速度比 container 慢一個量級、會直接成為 bottleneck。</li>
</ol>
<p>跨平台等效：AWS Enterprise Support + TAM、Azure Premier Support + CSAM 都有對等服務、但能即時動用工程師補容量的程度跟客戶等級綁定。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想對應 surge load → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">08.6 incident severity trigger</a></li>
<li>想設計降級策略 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access</a> + <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a></li>
<li>想評估 vendor 支援 → <a href="/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 operations control service selection</a></li>
<li>對照可預測峰值案例 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud</a></li>
<li><a href="https://cloud.google.com/customer-reliability-engineering">Google Customer Reliability Engineering</a></li>
</ul>
]]></content:encoded></item><item><title>2.C8 Meta：TAO 社交圖快取演進</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-tao-social-graph-cache-evolution/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-tao-social-graph-cache-evolution/</guid><description>&lt;p>這個案例的核心責任是說明快取在高關聯查詢場景會接近資料庫層角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Meta TAO 用於社交圖讀取，演進重點在一致性、可擴展性與資料關聯查詢效率。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當查詢負載是高度關聯圖資料，快取策略需從 key-value 轉向資料模型治理。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把資料關聯模型納入快取鍵設計。&lt;/li>
&lt;li>以一致性窗口設計更新策略。&lt;/li>
&lt;li>定期驗證讀取正確性與延遲目標。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-design/" data-link-title="1.2 Schema Design 與資料建模" data-link-desc="整理 table、index、key、partition、denormalization 與命名規則">1.2&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2013/06/25/core-infra/tao-the-power-of-the-graph/">TAO: Facebook&amp;rsquo;s Distributed Data Store&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明快取在高關聯查詢場景會接近資料庫層角色。</p>
<h2 id="觀察">觀察</h2>
<p>Meta TAO 用於社交圖讀取，演進重點在一致性、可擴展性與資料關聯查詢效率。</p>
<h2 id="判讀">判讀</h2>
<p>當查詢負載是高度關聯圖資料，快取策略需從 key-value 轉向資料模型治理。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把資料關聯模型納入快取鍵設計。</li>
<li>以一致性窗口設計更新策略。</li>
<li>定期驗證讀取正確性與延遲目標。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2</a> 與 <a href="/blog/backend/01-database/schema-design/" data-link-title="1.2 Schema Design 與資料建模" data-link-desc="整理 table、index、key、partition、denormalization 與命名規則">1.2</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2013/06/25/core-infra/tao-the-power-of-the-graph/">TAO: Facebook&rsquo;s Distributed Data Store</a></li>
</ul>
]]></content:encoded></item><item><title>3.C8 Cloudflare：Queues 全球交付模型</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/</guid><description>&lt;p>這個案例的核心責任是把 queue 選型從單區域傳遞提升為全球交付治理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Cloudflare Queues 以邊緣網路為背景，提供事件傳遞與 consumer 處理能力。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>全球部署下，queue 模型要同時考慮延遲、重試語義與跨區運維一致性。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>明確設定 delivery semantics 與重試策略。&lt;/li>
&lt;li>把 consumer 行為與死信處理流程標準化。&lt;/li>
&lt;li>將 queue lag 與失敗率接入平台觀測。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/introducing-cloudflare-queues/">Introducing Cloudflare Queues&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 queue 選型從單區域傳遞提升為全球交付治理。</p>
<h2 id="觀察">觀察</h2>
<p>Cloudflare Queues 以邊緣網路為背景，提供事件傳遞與 consumer 處理能力。</p>
<h2 id="判讀">判讀</h2>
<p>全球部署下，queue 模型要同時考慮延遲、重試語義與跨區運維一致性。</p>
<h2 id="策略">策略</h2>
<ol>
<li>明確設定 delivery semantics 與重試策略。</li>
<li>把 consumer 行為與死信處理流程標準化。</li>
<li>將 queue lag 與失敗率接入平台觀測。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4</a> 與 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/introducing-cloudflare-queues/">Introducing Cloudflare Queues</a></li>
</ul>
]]></content:encoded></item><item><title>4.C8 Airbnb：Kubernetes 規模化下的觀測訊號治理</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/</guid><description>&lt;p>這個案例的核心責任是把平台擴縮行為轉成可觀測治理問題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 在 Kubernetes 規模化過程強調動態擴縮，代表觀測系統需要追上容量與拓撲變化。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>若訊號模型無法反映動態叢集，告警與容量判讀容易失真。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>將叢集層指標與服務層指標分開治理。&lt;/li>
&lt;li>在擴縮流程中保留關鍵健康訊號。&lt;/li>
&lt;li>用回溯報表驗證擴縮與事故關聯。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把平台擴縮行為轉成可觀測治理問題。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 在 Kubernetes 規模化過程強調動態擴縮，代表觀測系統需要追上容量與拓撲變化。</p>
<h2 id="判讀">判讀</h2>
<p>若訊號模型無法反映動態叢集，告警與容量判讀容易失真。</p>
<h2 id="策略">策略</h2>
<ol>
<li>將叢集層指標與服務層指標分開治理。</li>
<li>在擴縮流程中保留關鍵健康訊號。</li>
<li>用回溯報表驗證擴縮與事故關聯。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb</a></li>
</ul>
]]></content:encoded></item><item><title>8.8 Stream：Feeds 與 Chat</title><link>https://tarrragon.github.io/blog/go/08-case-studies/stream/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/stream/</guid><description>&lt;p>Stream 的官方案例很適合教學用途，因為它把 Go 的幾個核心優勢講得很直接：ecosystem、easy onboarding、fast performance、solid support for concurrency 與 productive programming environment。官方案例還特別提到，這讓一個小團隊能支撐超過 5 億使用者的 feeds 與 chat。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/stream">Stream case study&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/GetStream/getstream-go">Official Go SDK for Stream&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很適合即時 feed 與 chat 這種高事件量服務。&lt;/li>
&lt;li>小團隊也能利用 Go 把服務做大。&lt;/li>
&lt;li>SDK 與 server-side service 都能用同一套語言思維來維護。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/GetStream/getstream-go">GetStream/getstream-go&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這個 Go SDK 很適合拿來看 request/response model、client 設計、testing 與 OpenAPI codegen 的邊界。&lt;/p></description><content:encoded><![CDATA[<p>Stream 的官方案例很適合教學用途，因為它把 Go 的幾個核心優勢講得很直接：ecosystem、easy onboarding、fast performance、solid support for concurrency 與 productive programming environment。官方案例還特別提到，這讓一個小團隊能支撐超過 5 億使用者的 feeds 與 chat。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/stream">Stream case study</a></li>
<li><a href="https://github.com/GetStream/getstream-go">Official Go SDK for Stream</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很適合即時 feed 與 chat 這種高事件量服務。</li>
<li>小團隊也能利用 Go 把服務做大。</li>
<li>SDK 與 server-side service 都能用同一套語言思維來維護。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/GetStream/getstream-go">GetStream/getstream-go</a></li>
</ul>
<p>這個 Go SDK 很適合拿來看 request/response model、client 設計、testing 與 OpenAPI codegen 的邊界。</p>
]]></content:encoded></item><item><title>模組八：Go 案例與讀碼路線</title><link>https://tarrragon.github.io/blog/go/08-case-studies/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/</guid><description>&lt;p>這個模組把前面學到的 Go 能力放回真實世界：哪些公司把 Go 用在什麼服務裡、他們為什麼選 Go、以及公開原始碼長什麼樣子。語法學習完成後，案例能幫讀者把語言能力、服務場景與選型條件對齊。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/go/08-case-studies/selection-patterns/" data-link-title="8.0 Go 的選型案例總覽" data-link-desc="用真實案例辨識 Go 常出現的服務選型條件">8.0&lt;/a>&lt;/td>
 &lt;td>Go 的選型案例總覽&lt;/td>
 &lt;td>用服務壓力辨識 Go 常出現的選型條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/google/" data-link-title="8.1 Google：大規模微服務與索引服務" data-link-desc="看 Go 如何支撐 Google 的大規模微服務與資料索引">8.1&lt;/a>&lt;/td>
 &lt;td>Google：大規模微服務與索引服務&lt;/td>
 &lt;td>看懂 Go 如何支撐大規模搜尋與資料處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/paypal/" data-link-title="8.2 PayPal：支付平台與 NoSQL / build pipelines" data-link-desc="看 Go 如何處理支付平台、NoSQL proxy 與內部工程流水線">8.2&lt;/a>&lt;/td>
 &lt;td>PayPal：支付平台與 NoSQL / build pipelines&lt;/td>
 &lt;td>看懂 Go 如何處理複雜系統與多執行緒邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/dropbox/" data-link-title="8.3 Dropbox：從 Python 遷移到 Go" data-link-desc="看性能關鍵後端如何從 Python 逐步轉向 Go">8.3&lt;/a>&lt;/td>
 &lt;td>Dropbox：從 Python 遷移到 Go&lt;/td>
 &lt;td>看懂性能關鍵後端如何逐步轉向 Go&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/microsoft/" data-link-title="8.4 Microsoft：雲端基礎設施的一部分" data-link-desc="看 Go 如何支撐雲端基礎設施與平台工具">8.4&lt;/a>&lt;/td>
 &lt;td>Microsoft：雲端基礎設施的一部分&lt;/td>
 &lt;td>看懂 Go 如何支撐 cloud infrastructure&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/twitch/" data-link-title="8.5 Twitch：直播與聊天室系統" data-link-desc="看 Go 如何服務低延遲、高併發的即時系統">8.5&lt;/a>&lt;/td>
 &lt;td>Twitch：直播與聊天室系統&lt;/td>
 &lt;td>看懂 Go 如何服務低延遲、高併發的即時系統&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/cloudflare/" data-link-title="8.6 Cloudflare：DNS、SSL 與長連線服務" data-link-desc="看 Go 如何處理大量連線、網路邊界與高延遲環境">8.6&lt;/a>&lt;/td>
 &lt;td>Cloudflare：DNS、SSL 與長連線服務&lt;/td>
 &lt;td>看懂 Go 如何處理網路邊界與大量連線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/cockroach-labs/" data-link-title="8.7 Cockroach Labs：分散式 SQL 資料庫" data-link-desc="看 Go 如何支撐分散式資料庫與高一致性系統">8.7&lt;/a>&lt;/td>
 &lt;td>Cockroach Labs：分散式 SQL 資料庫&lt;/td>
 &lt;td>看懂 Go 如何支撐高一致性、高複雜度系統&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/stream/" data-link-title="8.8 Stream：Feeds 與 Chat" data-link-desc="看 Go 如何支撐 feeds、chat 與即時訊息 SDK">8.8&lt;/a>&lt;/td>
 &lt;td>Stream：Feeds 與 Chat&lt;/td>
 &lt;td>看懂 Go 如何支撐大規模即時訊息服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/cloudwego/" data-link-title="8.9 ByteDance / CloudWeGo：微服務基礎設施" data-link-desc="看 Go 如何從單一服務語言沉澱成微服務治理與框架">8.9&lt;/a>&lt;/td>
 &lt;td>ByteDance / CloudWeGo：微服務基礎設施&lt;/td>
 &lt;td>看懂 Go 如何沉澱成微服務治理與框架&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/" data-link-title="8.10 Go 的高併發服務案例" data-link-desc="從即時服務、邊緣網路與資料平台辨識 Go 的高併發使用情境">8.10&lt;/a>&lt;/td>
 &lt;td>Go 的高併發服務案例&lt;/td>
 &lt;td>從長連線、代理、背景處理與資料服務辨識並發壓力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/open-source-code-reading/" data-link-title="8.11 Go 公開原始碼讀碼路線" data-link-desc="用固定順序閱讀成熟 Go 專案的入口、package、並發與測試">8.11&lt;/a>&lt;/td>
 &lt;td>Go 公開原始碼讀碼路線&lt;/td>
 &lt;td>用入口、組裝、邊界、並發 owner 與測試建立讀碼順序&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例分類的閱讀方式">案例分類的閱讀方式&lt;/h2>
&lt;p>案例分類的核心原則是先看服務壓力，再看公司名稱。Google、PayPal、Dropbox、Microsoft、Twitch、Cloudflare、Cockroach Labs、Stream 與 CloudWeGo 代表的是不同工程條件：大規模平台、高併發即時服務、效能敏感遷移、分散式資料系統與微服務治理。&lt;/p>
&lt;p>大規模平台案例通常要觀察服務形狀是否一致：入口、設定、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log&lt;/a>、部署與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/health-check-liveness/" data-link-title="Liveness" data-link-desc="說明平台如何判斷 process 是否仍然存活，以及何時應重啟">health check&lt;/a> 是否能被很多團隊共用。高併發即時案例通常要觀察連線是否長時間存在，以及 server 是否需要管理大量 client 狀態。效能敏感遷移案例通常要觀察瓶頸是否集中在清楚邊界。分散式基礎設施案例則要觀察主要問題是否落在多節點協調與可靠性。&lt;/p>
&lt;p>這張表是入口索引。讀每家公司案例時，應回到具體章節對照：選型判斷看 &lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/selection-patterns/" data-link-title="8.0 Go 的選型案例總覽" data-link-desc="用真實案例辨識 Go 常出現的服務選型條件">Go 的選型案例總覽&lt;/a>，並發服務看 &lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/" data-link-title="8.10 Go 的高併發服務案例" data-link-desc="從即時服務、邊緣網路與資料平台辨識 Go 的高併發使用情境">Go 的高併發服務案例&lt;/a>，公開原始碼則依照 &lt;a href="https://tarrragon.github.io/blog/go/08-case-studies/open-source-code-reading/" data-link-title="8.11 Go 公開原始碼讀碼路線" data-link-desc="用固定順序閱讀成熟 Go 專案的入口、package、並發與測試">Go 公開原始碼讀碼路線&lt;/a> 逐層閱讀。&lt;/p></description><content:encoded><![CDATA[<p>這個模組把前面學到的 Go 能力放回真實世界：哪些公司把 Go 用在什麼服務裡、他們為什麼選 Go、以及公開原始碼長什麼樣子。語法學習完成後，案例能幫讀者把語言能力、服務場景與選型條件對齊。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/go/08-case-studies/selection-patterns/" data-link-title="8.0 Go 的選型案例總覽" data-link-desc="用真實案例辨識 Go 常出現的服務選型條件">8.0</a></td>
          <td>Go 的選型案例總覽</td>
          <td>用服務壓力辨識 Go 常出現的選型條件</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/google/" data-link-title="8.1 Google：大規模微服務與索引服務" data-link-desc="看 Go 如何支撐 Google 的大規模微服務與資料索引">8.1</a></td>
          <td>Google：大規模微服務與索引服務</td>
          <td>看懂 Go 如何支撐大規模搜尋與資料處理</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/paypal/" data-link-title="8.2 PayPal：支付平台與 NoSQL / build pipelines" data-link-desc="看 Go 如何處理支付平台、NoSQL proxy 與內部工程流水線">8.2</a></td>
          <td>PayPal：支付平台與 NoSQL / build pipelines</td>
          <td>看懂 Go 如何處理複雜系統與多執行緒邊界</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/dropbox/" data-link-title="8.3 Dropbox：從 Python 遷移到 Go" data-link-desc="看性能關鍵後端如何從 Python 逐步轉向 Go">8.3</a></td>
          <td>Dropbox：從 Python 遷移到 Go</td>
          <td>看懂性能關鍵後端如何逐步轉向 Go</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/microsoft/" data-link-title="8.4 Microsoft：雲端基礎設施的一部分" data-link-desc="看 Go 如何支撐雲端基礎設施與平台工具">8.4</a></td>
          <td>Microsoft：雲端基礎設施的一部分</td>
          <td>看懂 Go 如何支撐 cloud infrastructure</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/twitch/" data-link-title="8.5 Twitch：直播與聊天室系統" data-link-desc="看 Go 如何服務低延遲、高併發的即時系統">8.5</a></td>
          <td>Twitch：直播與聊天室系統</td>
          <td>看懂 Go 如何服務低延遲、高併發的即時系統</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/cloudflare/" data-link-title="8.6 Cloudflare：DNS、SSL 與長連線服務" data-link-desc="看 Go 如何處理大量連線、網路邊界與高延遲環境">8.6</a></td>
          <td>Cloudflare：DNS、SSL 與長連線服務</td>
          <td>看懂 Go 如何處理網路邊界與大量連線</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/cockroach-labs/" data-link-title="8.7 Cockroach Labs：分散式 SQL 資料庫" data-link-desc="看 Go 如何支撐分散式資料庫與高一致性系統">8.7</a></td>
          <td>Cockroach Labs：分散式 SQL 資料庫</td>
          <td>看懂 Go 如何支撐高一致性、高複雜度系統</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/stream/" data-link-title="8.8 Stream：Feeds 與 Chat" data-link-desc="看 Go 如何支撐 feeds、chat 與即時訊息 SDK">8.8</a></td>
          <td>Stream：Feeds 與 Chat</td>
          <td>看懂 Go 如何支撐大規模即時訊息服務</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/cloudwego/" data-link-title="8.9 ByteDance / CloudWeGo：微服務基礎設施" data-link-desc="看 Go 如何從單一服務語言沉澱成微服務治理與框架">8.9</a></td>
          <td>ByteDance / CloudWeGo：微服務基礎設施</td>
          <td>看懂 Go 如何沉澱成微服務治理與框架</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/high-concurrency-services/" data-link-title="8.10 Go 的高併發服務案例" data-link-desc="從即時服務、邊緣網路與資料平台辨識 Go 的高併發使用情境">8.10</a></td>
          <td>Go 的高併發服務案例</td>
          <td>從長連線、代理、背景處理與資料服務辨識並發壓力</td>
      </tr>
      <tr>
          <td><a href="/blog/go/08-case-studies/open-source-code-reading/" data-link-title="8.11 Go 公開原始碼讀碼路線" data-link-desc="用固定順序閱讀成熟 Go 專案的入口、package、並發與測試">8.11</a></td>
          <td>Go 公開原始碼讀碼路線</td>
          <td>用入口、組裝、邊界、並發 owner 與測試建立讀碼順序</td>
      </tr>
  </tbody>
</table>
<h2 id="案例分類的閱讀方式">案例分類的閱讀方式</h2>
<p>案例分類的核心原則是先看服務壓力，再看公司名稱。Google、PayPal、Dropbox、Microsoft、Twitch、Cloudflare、Cockroach Labs、Stream 與 CloudWeGo 代表的是不同工程條件：大規模平台、高併發即時服務、效能敏感遷移、分散式資料系統與微服務治理。</p>
<p>大規模平台案例通常要觀察服務形狀是否一致：入口、設定、<a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a>、部署與 <a href="/blog/backend/knowledge-cards/health-check-liveness/" data-link-title="Liveness" data-link-desc="說明平台如何判斷 process 是否仍然存活，以及何時應重啟">health check</a> 是否能被很多團隊共用。高併發即時案例通常要觀察連線是否長時間存在，以及 server 是否需要管理大量 client 狀態。效能敏感遷移案例通常要觀察瓶頸是否集中在清楚邊界。分散式基礎設施案例則要觀察主要問題是否落在多節點協調與可靠性。</p>
<p>這張表是入口索引。讀每家公司案例時，應回到具體章節對照：選型判斷看 <a href="/blog/go/08-case-studies/selection-patterns/" data-link-title="8.0 Go 的選型案例總覽" data-link-desc="用真實案例辨識 Go 常出現的服務選型條件">Go 的選型案例總覽</a>，並發服務看 <a href="/blog/go/08-case-studies/high-concurrency-services/" data-link-title="8.10 Go 的高併發服務案例" data-link-desc="從即時服務、邊緣網路與資料平台辨識 Go 的高併發使用情境">Go 的高併發服務案例</a>，公開原始碼則依照 <a href="/blog/go/08-case-studies/open-source-code-reading/" data-link-title="8.11 Go 公開原始碼讀碼路線" data-link-desc="用固定順序閱讀成熟 Go 專案的入口、package、並發與測試">Go 公開原始碼讀碼路線</a> 逐層閱讀。</p>
<h2 id="這個模組的用途">這個模組的用途</h2>
<ul>
<li>幫讀者把 Go 的抽象能力對回真實服務</li>
<li>幫讀者確認 Go 常落在哪些產品與系統邊界</li>
<li>幫讀者建立讀公開原始碼的路線圖</li>
<li>幫讀者把「案例」與「實作細節」連起來</li>
</ul>
<h2 id="建議閱讀順序">建議閱讀順序</h2>
<ol>
<li>先看 Google 與 PayPal，理解大規模服務與複雜平台怎麼選 Go</li>
<li>再看 Dropbox、Microsoft、Twitch、Cloudflare，理解性能、即時與基礎設施場景</li>
<li>接著看 Cockroach Labs、Stream、CloudWeGo，理解更極端的高併發與分散式系統</li>
<li>最後再回頭看自己的服務場景，判斷哪些模式值得借用</li>
</ol>
]]></content:encoded></item><item><title>9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/</guid><description>&lt;p>這個案例的核心責任是說明「事件交付系統的容量規劃，靠 managed service 卸載 vs 自管 broker」的長期成本對照。Spotify 從 Kafka 遷到 Pub/Sub 的驅動力是 &lt;em>容量規劃的工程成本&lt;/em> 在 sustained growth 下變得不划算、Kafka 能力本身不是瓶頸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Spotify 在 Google Cloud 的遷移敘述（引自 &lt;a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&amp;rsquo;s journey to cloud&lt;/a>）：&lt;/p>
&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>7500 萬 + 用戶（遷移時期）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移系統&lt;/td>
 &lt;td>Event Delivery System（事件交付）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷出技術&lt;/td>
 &lt;td>自管 Apache Kafka&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷入技術&lt;/td>
 &lt;td>Google Cloud Pub/Sub&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>大數據生態&lt;/td>
 &lt;td>BigQuery / Dataflow / Dataproc / Pub/Sub&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵動機：「moving event delivery to a managed service」— 卸下 Kafka broker 的容量規劃與運維負擔。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Spotify 遷移揭露三個 broker 容量規劃的長期工程問題。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>自管 broker 的容量規劃是長期 tax&lt;/strong>：Kafka cluster 需要 partition planning、broker 數量、副本因子、disk capacity、network bandwidth、ZooKeeper / KRaft 治理 — 每個維度都要持續規劃、每次擴容都是工程專案。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 broker basics 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本評估。&lt;/li>
&lt;li>&lt;strong>managed service 的容量是 trade-off、不是免費午餐&lt;/strong>：Pub/Sub 自動 scaling、但 vendor lock-in、cost-per-message 累積、message ordering / latency 特性跟 Kafka 不同。遷移本身要驗證 &lt;em>業務語意&lt;/em> 跟 Pub/Sub 兼容。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移本身是容量規劃題目&lt;/strong>：把 7500 萬用戶的事件交付從 A 平台搬到 B 平台、不能停機、不能丟 message。這個遷移過程本身就是高併發容量工程。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a> 的同類流程。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Spotify 這個決定不是「Kafka 不好」、是「Spotify 規模下、自管 Kafka 的工程投入不划算」。對中小團隊、自管 Kafka 可能是更便宜的選項。讀案例時要看 &lt;em>規模門檻&lt;/em> 跟 &lt;em>團隊能力&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>broker 自管 vs managed 是長期 TCO 評估&lt;/strong>：算「平日運維 + 容量擴容 + 故障處理 + 升級遷移」的人力成本、不只算「broker 雲端費用」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移分階段：dual write → shadow → cutover&lt;/strong>：先寫兩邊、驗證一致性、再切流量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a> 的同類流程。&lt;/li>
&lt;li>&lt;strong>業務語意對映是遷移關鍵&lt;/strong>：Kafka 的 partition / offset / consumer group 在 Pub/Sub 對映成不同概念（subscription / ordering key / message attribute）、不是 1:1。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS SNS / SQS / Kinesis、Amazon MSK（managed Kafka）、Azure Service Bus / Event Hubs / Event Grid 都是對等候選。差異是 message ordering 保證、delivery guarantee、cost model。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「事件交付系統的容量規劃，靠 managed service 卸載 vs 自管 broker」的長期成本對照。Spotify 從 Kafka 遷到 Pub/Sub 的驅動力是 <em>容量規劃的工程成本</em> 在 sustained growth 下變得不划算、Kafka 能力本身不是瓶頸。</p>
<h2 id="觀察">觀察</h2>
<p>Spotify 在 Google Cloud 的遷移敘述（引自 <a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&rsquo;s journey to cloud</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶規模</td>
          <td>7500 萬 + 用戶（遷移時期）</td>
      </tr>
      <tr>
          <td>遷移系統</td>
          <td>Event Delivery System（事件交付）</td>
      </tr>
      <tr>
          <td>遷出技術</td>
          <td>自管 Apache Kafka</td>
      </tr>
      <tr>
          <td>遷入技術</td>
          <td>Google Cloud Pub/Sub</td>
      </tr>
      <tr>
          <td>大數據生態</td>
          <td>BigQuery / Dataflow / Dataproc / Pub/Sub</td>
      </tr>
  </tbody>
</table>
<p>關鍵動機：「moving event delivery to a managed service」— 卸下 Kafka broker 的容量規劃與運維負擔。</p>
<h2 id="判讀">判讀</h2>
<p>Spotify 遷移揭露三個 broker 容量規劃的長期工程問題。</p>
<ol>
<li><strong>自管 broker 的容量規劃是長期 tax</strong>：Kafka cluster 需要 partition planning、broker 數量、副本因子、disk capacity、network bandwidth、ZooKeeper / KRaft 治理 — 每個維度都要持續規劃、每次擴容都是工程專案。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 broker basics 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本評估。</li>
<li><strong>managed service 的容量是 trade-off、不是免費午餐</strong>：Pub/Sub 自動 scaling、但 vendor lock-in、cost-per-message 累積、message ordering / latency 特性跟 Kafka 不同。遷移本身要驗證 <em>業務語意</em> 跟 Pub/Sub 兼容。對應 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics</a>。</li>
<li><strong>遷移本身是容量規劃題目</strong>：把 7500 萬用戶的事件交付從 A 平台搬到 B 平台、不能停機、不能丟 message。這個遷移過程本身就是高併發容量工程。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的同類流程。</li>
</ol>
<p>需要警惕：Spotify 這個決定不是「Kafka 不好」、是「Spotify 規模下、自管 Kafka 的工程投入不划算」。對中小團隊、自管 Kafka 可能是更便宜的選項。讀案例時要看 <em>規模門檻</em> 跟 <em>團隊能力</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>broker 自管 vs managed 是長期 TCO 評估</strong>：算「平日運維 + 容量擴容 + 故障處理 + 升級遷移」的人力成本、不只算「broker 雲端費用」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移分階段：dual write → shadow → cutover</strong>：先寫兩邊、驗證一致性、再切流量。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的同類流程。</li>
<li><strong>業務語意對映是遷移關鍵</strong>：Kafka 的 partition / offset / consumer group 在 Pub/Sub 對映成不同概念（subscription / ordering key / message attribute）、不是 1:1。</li>
</ol>
<p>跨平台等效：AWS SNS / SQS / Kinesis、Amazon MSK（managed Kafka）、Azure Service Bus / Event Hubs / Event Grid 都是對等候選。差異是 message ordering 保證、delivery guarantee、cost model。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想評估 broker 自管 vs managed → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>想做大規模 message 系統遷移 → <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的對等流程</li>
<li>想理解 broker 容量規劃 → <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics</a></li>
<li>對照其他事件型負載 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&rsquo;s journey to cloud: why Spotify migrated its event delivery system from Kafka to Google Cloud Pub/Sub</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/spotify-chooses-google-cloud-platform-to-power-data-infrastructure/">Spotify chooses Google Cloud Platform</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/spotifys-experiments-with-stream-processing-on-google-cloud-dataflow">Spotify&rsquo;s experiments with stream processing on Google Cloud Dataflow</a></li>
</ul>
]]></content:encoded></item><item><title>2.C9 反例：快取切換引發 Stampede 回歸</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/failure-cache-stampede-rollout-regression/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/failure-cache-stampede-rollout-regression/</guid><description>&lt;p>這個反例的核心責任是說明快取轉換最常失敗在回源保護不足。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>一次看似低風險的 cache key 或 TTL 切換，會讓熱門資料同時 miss。使用者看到的是 API 變慢與錯誤率上升，資料庫看到的是原本被快取吸收的流量突然全部回源。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>快取切換如果沒有 warmup、singleflight、節流與降級保護，miss 會引發重試，重試又會增加 origin 壓力。影響面是讀取路徑同時失去緩衝，單一 key 層級的思考抓不到全貌。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>回退不應只把程式版本切回去。若新舊快取 key、TTL 或序列化格式已經混在一起，回退還要處理資料可讀性與回源壓力。實務上要先降載或恢復舊 key 讀取，再逐步清理新策略留下的快取狀態。&lt;/p>
&lt;h2 id="快取專屬告警條件">快取專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>熱門 key miss 同步上升，且 origin QPS 快速超過平日基線&lt;/li>
&lt;li>response time 拉長並伴隨重試流量增加&lt;/li>
&lt;li>stale read 與 cache miss 同時惡化&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明快取轉換最常失敗在回源保護不足。</p>
<h2 id="事故長相">事故長相</h2>
<p>一次看似低風險的 cache key 或 TTL 切換，會讓熱門資料同時 miss。使用者看到的是 API 變慢與錯誤率上升，資料庫看到的是原本被快取吸收的流量突然全部回源。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>快取切換如果沒有 warmup、singleflight、節流與降級保護，miss 會引發重試，重試又會增加 origin 壓力。影響面是讀取路徑同時失去緩衝，單一 key 層級的思考抓不到全貌。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>回退不應只把程式版本切回去。若新舊快取 key、TTL 或序列化格式已經混在一起，回退還要處理資料可讀性與回源壓力。實務上要先降載或恢復舊 key 讀取，再逐步清理新策略留下的快取狀態。</p>
<h2 id="快取專屬告警條件">快取專屬告警條件</h2>
<ul>
<li>熱門 key miss 同步上升，且 origin QPS 快速超過平日基線</li>
<li>response time 拉長並伴隨重試流量增加</li>
<li>stale read 與 cache miss 同時惡化</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/02-cache-redis/cache-aside/" data-link-title="2.2 cache aside 與失效策略" data-link-desc="整理 read-through 思路、cache miss 與 invalidation">2.2</a> 與 <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24</a>。</p>
]]></content:encoded></item><item><title>3.C9 反例：Queue 語義切換誤配</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/</guid><description>&lt;p>這個反例的核心責任是說明 broker 遷移失敗常發生在語義假設錯置。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>切換 broker 或 consumer group 後，表面上訊息仍然被送達，但業務資料開始出現重複扣款、重複寄信、狀態漏更新這類問題。這種事故很難只靠 queue depth 判斷，因為錯誤發生在「處理語義」而不是「是否有訊息」。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>舊系統若依賴特定 offset 行為、重試節奏或 consumer idempotency，新系統即使名稱上提供相近 delivery semantics，也可能在失敗重播時產生不同結果。語義誤配會沿著下游資料寫入擴散。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>回退前要先確認哪一段資料已經被新語義處理過。若直接切回舊 broker，可能讓同一批事件再次被處理。更穩定的做法是先凍結新 consumer，保留 offset 對照與 replay 範圍，再決定補償或重播。&lt;/p>
&lt;h2 id="queue-專屬告警條件">Queue 專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>下游 reconciliation 同時出現重複與遺漏&lt;/li>
&lt;li>DLQ 激增且重播後仍回到相同錯誤&lt;/li>
&lt;li>consumer lag 下降但業務結果沒有收斂&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 broker 遷移失敗常發生在語義假設錯置。</p>
<h2 id="事故長相">事故長相</h2>
<p>切換 broker 或 consumer group 後，表面上訊息仍然被送達，但業務資料開始出現重複扣款、重複寄信、狀態漏更新這類問題。這種事故很難只靠 queue depth 判斷，因為錯誤發生在「處理語義」而不是「是否有訊息」。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>舊系統若依賴特定 offset 行為、重試節奏或 consumer idempotency，新系統即使名稱上提供相近 delivery semantics，也可能在失敗重播時產生不同結果。語義誤配會沿著下游資料寫入擴散。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>回退前要先確認哪一段資料已經被新語義處理過。若直接切回舊 broker，可能讓同一批事件再次被處理。更穩定的做法是先凍結新 consumer，保留 offset 對照與 replay 範圍，再決定補償或重播。</p>
<h2 id="queue-專屬告警條件">Queue 專屬告警條件</h2>
<ul>
<li>下游 reconciliation 同時出現重複與遺漏</li>
<li>DLQ 激增且重播後仍回到相同錯誤</li>
<li>consumer lag 下降但業務結果沒有收斂</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4</a> 與 <a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10</a>。</p>
]]></content:encoded></item><item><title>4.C9 反例：OTel 遷移後訊號漂移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/</guid><description>&lt;p>這個反例的核心責任是說明 observability 遷移失敗常以語意漂移形式出現，資料丟失反而少見。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>OTel 切換後，儀表板看起來都有資料，但 on-call 開始收到不同告警，SLO burn rate 與舊系統長期對不上。同一個事故在新舊管線裡被歸到不同 service、不同 label 或不同 latency bucket。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>觀測資料是事故判讀的入口。若 metric 名稱、label、sampling、aggregation 不一致，團隊會對同一個現象做出不同判斷，甚至在錯誤訊號上回退服務。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>觀測遷移的回退不一定是回到舊 agent。更重要的是保留新舊訊號對照，先停止讓新管線主導告警與 SLO 判定，再修正語意對齊。若直接關掉新管線，反而會失去分析漂移原因的證據。&lt;/p>
&lt;h2 id="觀測專屬告警條件">觀測專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>新舊管線對同一服務的 error rate 長期偏離&lt;/li>
&lt;li>missing span 或 missing metric 比例持續上升&lt;/li>
&lt;li>alert 噪音增加，但事故量沒有對應增加&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 observability 遷移失敗常以語意漂移形式出現，資料丟失反而少見。</p>
<h2 id="事故長相">事故長相</h2>
<p>OTel 切換後，儀表板看起來都有資料，但 on-call 開始收到不同告警，SLO burn rate 與舊系統長期對不上。同一個事故在新舊管線裡被歸到不同 service、不同 label 或不同 latency bucket。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>觀測資料是事故判讀的入口。若 metric 名稱、label、sampling、aggregation 不一致，團隊會對同一個現象做出不同判斷，甚至在錯誤訊號上回退服務。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>觀測遷移的回退不一定是回到舊 agent。更重要的是保留新舊訊號對照，先停止讓新管線主導告警與 SLO 判定，再修正語意對齊。若直接關掉新管線，反而會失去分析漂移原因的證據。</p>
<h2 id="觀測專屬告警條件">觀測專屬告警條件</h2>
<ul>
<li>新舊管線對同一服務的 error rate 長期偏離</li>
<li>missing span 或 missing metric 比例持續上升</li>
<li>alert 噪音增加，但事故量沒有對應增加</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17</a> 與 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a>。</p>
]]></content:encoded></item><item><title>5.C9 反例：平台切流未先 Draining</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/</guid><description>&lt;p>這個反例的核心責任是說明部署平台切換失敗常在 connection lifecycle 管理——平台元件本身健康，事故來源是切換時序錯位。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>平台切流一開始看似成功，新的 instance 也通過 readiness，但長連線、背景工作與 load balancer 仍把流量送到即將下線的節點。使用者看到的是短時間大量 5xx、重連風暴與 timeout。&lt;/p>
&lt;p>典型 timeline：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>T+0&lt;/strong>：開始切流，新版本 pod readiness 通過，LB 開始導入流量。&lt;/li>
&lt;li>&lt;strong>T+30s&lt;/strong>：5xx spike 出現。舊 pod 的 endpoint 尚未從所有 kube-proxy / envoy 移除，部分客戶端仍打到舊 pod。舊 pod 同時收到 SIGTERM 開始 shutdown，在途請求被中斷。&lt;/li>
&lt;li>&lt;strong>T+2m&lt;/strong>：長連線客戶端偵測到斷線，觸發 reconnect。大量客戶端同時重連到新 pod，形成 reconnect storm。新 pod 的連線數瞬間飆高，部分 pod 因連線數超出預期開始 timeout。&lt;/li>
&lt;li>&lt;strong>T+5m&lt;/strong>：on-call 判斷切流失敗，決定回退。但回退操作需要時間——DNS 權重切回、LB 規則恢復、舊 pod 重新啟動。&lt;/li>
&lt;li>&lt;strong>T+15m&lt;/strong>：回退完成，舊版本重新接流量。但 reconnect storm 尚未收斂，連線數曲線仍高於 baseline，客戶端在新舊入口之間震盪。&lt;/li>
&lt;li>&lt;strong>T+30m&lt;/strong>：連線數逐漸回落，錯誤率回到 baseline。事故實際影響時間遠超切流本身。&lt;/li>
&lt;/ul>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>事故擴大的根因是 drain、idle timeout、health check、client retry 四者節奏錯位。每一對的不同步都會放大問題：&lt;/p>
&lt;p>&lt;strong>drain 與 endpoint 摘除不同步&lt;/strong>：pod 收到 SIGTERM 開始 shutdown，但 endpoint 還在 LB 的可用集合中（endpoint controller 同步有延遲）。這段窗口內新請求仍被導到即將關閉的 pod，產生 5xx。解法是 preStop hook 先等 endpoint 傳播（5-15 秒），再開始 graceful shutdown。&lt;/p>
&lt;p>&lt;strong>idle timeout 與 drain window 不同步&lt;/strong>：LB 的 idle timeout 設 60 秒，但 drain window 只有 30 秒。drain 結束後 pod 被強制終止，LB 側認為連線還活著（60 秒內不算 idle），繼續送流量到已不存在的 pod。結果是 LB 拿到 connection reset，觸發重試或回 502。&lt;/p>
&lt;p>&lt;strong>health check 與 readiness 語意不同步&lt;/strong>：LB health check 每 10 秒打一次，連續 3 次失敗才摘除。pod 已經 not-ready 但 LB 要 30 秒後才反映。這 30 秒窗口跟 drain window 疊加，讓舊 pod 在 shutdown 狀態下持續收到流量。&lt;/p>
&lt;p>&lt;strong>client retry 與 reconnect 策略不同步&lt;/strong>：客戶端偵測到連線中斷後立即重試（無 backoff），大量客戶端同時重連。如果客戶端沒有 jitter，重連請求會集中在同一毫秒到達，形成 thundering herd。&lt;/p>
&lt;p>這四組錯位在穩態下不會出現——穩態時 drain / timeout / health check 各自運作不衝突。只有在切流時四者同時被觸發，錯位才會互相放大。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>回退分兩個階段，性質不同、節奏不同、不能合併執行。&lt;/p>
&lt;p>&lt;strong>第一階段：凍結 + 恢復穩定路徑（分鐘級）&lt;/strong>。發現切流失敗的第一動作是停止下一批切流（freeze rollout），然後恢復舊入口權重（DNS 加權切回 / LB 規則回復）。新版本 pod 不立即關閉——保留作為對照證據，也避免關閉動作觸發第二波 reconnect。這個階段的目標是「讓震盪不擴大」，所有動作要在 5 分鐘內完成。&lt;/p>
&lt;p>&lt;strong>第二階段：等待收斂 + 修正錯位（小時級）&lt;/strong>。凍結後進入觀察狀態。reconnect storm 需要時間消化——客戶端逐漸穩定到舊入口、連線數曲線下降、5xx 回到 baseline。觀察指標：連線數曲線、reconnect rate、per-version error rate。三項都回到 baseline 且持續 N 分鐘（通常 10-15 分鐘），才算穩定。穩定後開始修正：找出 drain / timeout / health check / retry 的具體錯位點，修正後重新進入小範圍驗證。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明部署平台切換失敗常在 connection lifecycle 管理——平台元件本身健康，事故來源是切換時序錯位。</p>
<h2 id="事故長相">事故長相</h2>
<p>平台切流一開始看似成功，新的 instance 也通過 readiness，但長連線、背景工作與 load balancer 仍把流量送到即將下線的節點。使用者看到的是短時間大量 5xx、重連風暴與 timeout。</p>
<p>典型 timeline：</p>
<ul>
<li><strong>T+0</strong>：開始切流，新版本 pod readiness 通過，LB 開始導入流量。</li>
<li><strong>T+30s</strong>：5xx spike 出現。舊 pod 的 endpoint 尚未從所有 kube-proxy / envoy 移除，部分客戶端仍打到舊 pod。舊 pod 同時收到 SIGTERM 開始 shutdown，在途請求被中斷。</li>
<li><strong>T+2m</strong>：長連線客戶端偵測到斷線，觸發 reconnect。大量客戶端同時重連到新 pod，形成 reconnect storm。新 pod 的連線數瞬間飆高，部分 pod 因連線數超出預期開始 timeout。</li>
<li><strong>T+5m</strong>：on-call 判斷切流失敗，決定回退。但回退操作需要時間——DNS 權重切回、LB 規則恢復、舊 pod 重新啟動。</li>
<li><strong>T+15m</strong>：回退完成，舊版本重新接流量。但 reconnect storm 尚未收斂，連線數曲線仍高於 baseline，客戶端在新舊入口之間震盪。</li>
<li><strong>T+30m</strong>：連線數逐漸回落，錯誤率回到 baseline。事故實際影響時間遠超切流本身。</li>
</ul>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>事故擴大的根因是 drain、idle timeout、health check、client retry 四者節奏錯位。每一對的不同步都會放大問題：</p>
<p><strong>drain 與 endpoint 摘除不同步</strong>：pod 收到 SIGTERM 開始 shutdown，但 endpoint 還在 LB 的可用集合中（endpoint controller 同步有延遲）。這段窗口內新請求仍被導到即將關閉的 pod，產生 5xx。解法是 preStop hook 先等 endpoint 傳播（5-15 秒），再開始 graceful shutdown。</p>
<p><strong>idle timeout 與 drain window 不同步</strong>：LB 的 idle timeout 設 60 秒，但 drain window 只有 30 秒。drain 結束後 pod 被強制終止，LB 側認為連線還活著（60 秒內不算 idle），繼續送流量到已不存在的 pod。結果是 LB 拿到 connection reset，觸發重試或回 502。</p>
<p><strong>health check 與 readiness 語意不同步</strong>：LB health check 每 10 秒打一次，連續 3 次失敗才摘除。pod 已經 not-ready 但 LB 要 30 秒後才反映。這 30 秒窗口跟 drain window 疊加，讓舊 pod 在 shutdown 狀態下持續收到流量。</p>
<p><strong>client retry 與 reconnect 策略不同步</strong>：客戶端偵測到連線中斷後立即重試（無 backoff），大量客戶端同時重連。如果客戶端沒有 jitter，重連請求會集中在同一毫秒到達，形成 thundering herd。</p>
<p>這四組錯位在穩態下不會出現——穩態時 drain / timeout / health check 各自運作不衝突。只有在切流時四者同時被觸發，錯位才會互相放大。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>回退分兩個階段，性質不同、節奏不同、不能合併執行。</p>
<p><strong>第一階段：凍結 + 恢復穩定路徑（分鐘級）</strong>。發現切流失敗的第一動作是停止下一批切流（freeze rollout），然後恢復舊入口權重（DNS 加權切回 / LB 規則回復）。新版本 pod 不立即關閉——保留作為對照證據，也避免關閉動作觸發第二波 reconnect。這個階段的目標是「讓震盪不擴大」，所有動作要在 5 分鐘內完成。</p>
<p><strong>第二階段：等待收斂 + 修正錯位（小時級）</strong>。凍結後進入觀察狀態。reconnect storm 需要時間消化——客戶端逐漸穩定到舊入口、連線數曲線下降、5xx 回到 baseline。觀察指標：連線數曲線、reconnect rate、per-version error rate。三項都回到 baseline 且持續 N 分鐘（通常 10-15 分鐘），才算穩定。穩定後開始修正：找出 drain / timeout / health check / retry 的具體錯位點，修正後重新進入小範圍驗證。</p>
<p>第一階段的陷阱是「回退了但沒凍結」——回退流量的同時繼續推下一批切流，兩個動作互相衝突。第二階段的陷阱是「時間到了就解凍」——用時間而非指標判斷穩定，可能在連線數仍高時重新切流。</p>
<h2 id="這個事故教給後續章節什麼">這個事故教給後續章節什麼</h2>
<ul>
<li><strong>5.3 load balancer 合約</strong>的「切流告警條件」段：四條告警（批次 5xx、reconnect rate、RTO 超時、per-version error rate 偏離）直接來自這類事故的觀測需求。</li>
<li><strong>5.6 Platform Lifecycle Contract</strong>的「三種 Workload 的 Drain 差異」段：短 API、長連線、worker 的 drain 條件不同——這個事故揭露混用單一 drain window 的後果。</li>
<li><strong>5.8 Rollout/Drain/Rollback</strong>的「Traffic / Drain」段退場順序：readiness 先轉 not-ready → 保留 drain 窗口 → 確認連線數下降 → 終止進程，是從這類事故的 timeline 反推出來的。</li>
</ul>
<h2 id="部署專屬告警條件">部署專屬告警條件</h2>
<ul>
<li>切流批次內 5xx 突增（相對於前一批的升幅超過閾值）</li>
<li>長連線重連率快速上升（reconnect rate 超過 baseline N 倍）</li>
<li>rollback time 超過既定 RTO（執行回退後恢復時間超標）</li>
<li>per-version error rate 偏離（新舊版本 error rate 差距持續不收斂）</li>
</ul>
<p>這些告警的閾值要在 release plan 中先定義。切流期告警跟日常告警分流到不同 channel，避免日常 noise 淹沒切流期的關鍵訊號。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/load-balancer-contract/" data-link-title="5.3 load balancer 合約" data-link-desc="整理 idle timeout、draining 與 health check">5.3 load balancer 合約</a> 看流量契約與回退框架。回 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract</a> 看 drain 的 workload 分類。回 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR/Rollback Rehearsal</a> 看回退演練如何預防這類事故。</p>
]]></content:encoded></item><item><title>7.C9 反例：憑證輪替未分 Scope</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/failure-credential-rotation-without-scope/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/failure-credential-rotation-without-scope/</guid><description>&lt;p>這個反例的核心責任是說明 credential rotation 的失敗通常是治理節奏錯誤。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>憑證輪替完成後，多個服務同時開始認證失敗。問題不一定是新憑證錯，而是共用憑證牽涉太多服務，且各服務支援新舊憑證的時間窗口不同。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>secret、token、key 若沒有按作用域分開，輪替會變成一次性控制面變更。當一個系統先切新憑證、另一個系統還只認舊憑證，故障會沿著服務依賴快速擴散。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>憑證事故不能只把舊憑證放回去。若舊憑證已被視為風險來源，直接回放可能重新打開安全缺口。更穩定的做法是先分域隔離受影響服務，恢復雙憑證窗口，再逐批收斂。&lt;/p>
&lt;h2 id="資安專屬告警條件">資安專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>認證失敗同時跨多個 service boundary&lt;/li>
&lt;li>輪替失敗率上升並伴隨權限例外增加&lt;/li>
&lt;li>incident log 顯示 owner 與憑證作用域不清&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 credential rotation 的失敗通常是治理節奏錯誤。</p>
<h2 id="事故長相">事故長相</h2>
<p>憑證輪替完成後，多個服務同時開始認證失敗。問題不一定是新憑證錯，而是共用憑證牽涉太多服務，且各服務支援新舊憑證的時間窗口不同。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>secret、token、key 若沒有按作用域分開，輪替會變成一次性控制面變更。當一個系統先切新憑證、另一個系統還只認舊憑證，故障會沿著服務依賴快速擴散。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>憑證事故不能只把舊憑證放回去。若舊憑證已被視為風險來源，直接回放可能重新打開安全缺口。更穩定的做法是先分域隔離受影響服務，恢復雙憑證窗口，再逐批收斂。</p>
<h2 id="資安專屬告警條件">資安專屬告警條件</h2>
<ul>
<li>認證失敗同時跨多個 service boundary</li>
<li>輪替失敗率上升並伴隨權限例外增加</li>
<li>incident log 顯示 owner 與憑證作用域不清</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.6</a> 與 <a href="/blog/backend/07-security-data-protection/security-governance-exception-and-tripwire/" data-link-title="7.14 資安治理例外與 Tripwire" data-link-desc="定義例外管理、風險接受與重新評估觸發器">7.14</a>。</p>
]]></content:encoded></item><item><title>8.9 ByteDance / CloudWeGo：微服務基礎設施</title><link>https://tarrragon.github.io/blog/go/08-case-studies/cloudwego/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/cloudwego/</guid><description>&lt;p>CloudWeGo 是理解 Go 在大型公司內部如何演化成基礎設施層的好案例。官方介紹指出，它是 ByteDance Infrastructure Service Framework 團隊開源的 middleware 集合，核心關注是高性能、高擴展性、高可靠性與微服務溝通與治理。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.cloudwego.io/about/">CloudWeGo About&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cloudwego.io/blog/2023/06/15/cloudwego-a-leading-practice-for-building-enterprise-cloud-native-middleware/">CloudWeGo: a leading practice for building enterprise cloud native middleware&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.cloudwego.io/blog/2022/03/25/an-article-to-learn-about-bytedance-microservices-middleware-cloudwego/">An Article to Learn About ByteDance Microservices Middleware CloudWeGo&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 可以從單一服務語言，進一步變成微服務平台語言。&lt;/li>
&lt;li>大型服務常會把 RPC、HTTP、networking、serialization 拆成不同 middleware。&lt;/li>
&lt;li>Go 的簡潔語法與高性能 runtime 很適合做基礎設施層。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/cloudwego/kitex">cloudwego/kitex&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cloudwego/hertz">cloudwego/hertz&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/cloudwego/netpoll">cloudwego/netpoll&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這三個 repo 很適合對照第 4、6、7 模組，尤其是高併發控制、HTTP 邊界與 ports/adapters 的組織方式。&lt;/p></description><content:encoded><![CDATA[<p>CloudWeGo 是理解 Go 在大型公司內部如何演化成基礎設施層的好案例。官方介紹指出，它是 ByteDance Infrastructure Service Framework 團隊開源的 middleware 集合，核心關注是高性能、高擴展性、高可靠性與微服務溝通與治理。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://www.cloudwego.io/about/">CloudWeGo About</a></li>
<li><a href="https://www.cloudwego.io/blog/2023/06/15/cloudwego-a-leading-practice-for-building-enterprise-cloud-native-middleware/">CloudWeGo: a leading practice for building enterprise cloud native middleware</a></li>
<li><a href="https://www.cloudwego.io/blog/2022/03/25/an-article-to-learn-about-bytedance-microservices-middleware-cloudwego/">An Article to Learn About ByteDance Microservices Middleware CloudWeGo</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 可以從單一服務語言，進一步變成微服務平台語言。</li>
<li>大型服務常會把 RPC、HTTP、networking、serialization 拆成不同 middleware。</li>
<li>Go 的簡潔語法與高性能 runtime 很適合做基礎設施層。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/cloudwego/kitex">cloudwego/kitex</a></li>
<li><a href="https://github.com/cloudwego/hertz">cloudwego/hertz</a></li>
<li><a href="https://github.com/cloudwego/netpoll">cloudwego/netpoll</a></li>
</ul>
<p>這三個 repo 很適合對照第 4、6、7 模組，尤其是高併發控制、HTTP 邊界與 ports/adapters 的組織方式。</p>
]]></content:encoded></item><item><title>9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/</guid><description>&lt;p>這個案例的核心責任是提供「全球一致性 OLTP」的容量參考點。Spanner 是 Google 內部支撐 Ads、Play、Cloud Search 等服務的核心 DB、後來開放為 GCP 服務、是少數公開能撐每秒 10 億請求且維持強一致性的 OLTP 資料庫。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Spanner 公開數字（引自 &lt;a href="https://cloud.google.com/spanner">Spanner overview&lt;/a> / &lt;a href="https://cloud.google.com/spanner/docs/performance">Spanner performance docs&lt;/a>）：&lt;/p>
&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>&amp;gt; 10 億 requests / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Spanner Omni 區域峰值&lt;/td>
 &lt;td>數百萬 QPS、PB 級資料量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>線性擴展性&lt;/td>
 &lt;td>2 nodes → 45000 reads/sec、4 nodes → 90000 reads/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一致性模型&lt;/td>
 &lt;td>external consistency（強一致 + 線性化）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>代表性客戶：Google 內部所有支付、廣告計費、Play 商店、Search 索引；公開客戶包括 Blockchain.com、Niantic（部分服務）、Sharechat、ZEE5、Wayfair。&lt;/p>
&lt;p>關鍵設計：TrueTime API（GPS + 原子鐘）讓跨地區交易能維持 external consistency、不是 eventual。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Spanner 案例最值得讀的不是「能撐多大」、是「為什麼要這樣設計才能撐」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>線性擴展是 OLTP 的最高設計目標&lt;/strong>：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」這個 linear scaling 在傳統 OLTP（PostgreSQL、MySQL）做不到 — 因為 &lt;em>跨節點交易&lt;/em> 需要 coordinator、coordinator 是 bottleneck。Spanner 用 Paxos + TrueTime 把 coordinator 變成「拓樸感知的多 leader」、才達成線性。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的設計取捨。&lt;/li>
&lt;li>&lt;strong>強一致 vs 全球部署不是必須二選&lt;/strong>：CAP 定理常被解讀為「全球部署只能 eventual consistency」、Spanner 顯示「投入專屬硬體（GPS、原子鐘）+ 演算法（TrueTime）可以同時拿到 strong consistency + global distribution」。但這套硬體投資對其他 vendor 不容易複製。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的全球 OLTP 選項。&lt;/li>
&lt;li>&lt;strong>計費粒度 = 容量規劃顆粒&lt;/strong>：Spanner 早期最小單位是 100 processing units（pu）≈ 1 node、太大讓中小負載難以用。後來推出 100 pu 起跳的 granular sizing、讓容量規劃可以從小開始。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的容量單位選擇。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「10 億 req/sec」是 Google 內部的某個峰值瞬間、是 Spanner 服務 &lt;em>全部使用者加總&lt;/em>、不是單一 instance 數字。讀案例時要區分「全球聚合峰值」跟「單一客戶能拿到的最大配額」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>跨地區一致性需求要在設計初期決定&lt;/strong>：如果業務必需 strong consistency（金融、ticketing）、選 Spanner 等對等服務；如果 eventual 可接受（社群、推薦）、選 Cassandra / DynamoDB Global Tables 等更便宜的選項。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的全球一致性需求識別。&lt;/li>
&lt;li>&lt;strong>節點數即容量單位、預先規劃 sizing&lt;/strong>：Spanner 容量 = 節點數 × 單節點 QPS。每年 capacity review 主要在調節點數、不在調 schema。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>跨地區 latency 是強一致的代價&lt;/strong>：external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms。延遲敏感型業務不能用跨地區 strong consistency。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS Aurora DSQL（2024 推出、跨地區 strong consistency）、CockroachDB（自管）、TiDB（自管或 cloud）都是對等候選。差異是 TrueTime / 同等同步機制的成熟度。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「全球一致性 OLTP」的容量參考點。Spanner 是 Google 內部支撐 Ads、Play、Cloud Search 等服務的核心 DB、後來開放為 GCP 服務、是少數公開能撐每秒 10 億請求且維持強一致性的 OLTP 資料庫。</p>
<h2 id="觀察">觀察</h2>
<p>Spanner 公開數字（引自 <a href="https://cloud.google.com/spanner">Spanner overview</a> / <a href="https://cloud.google.com/spanner/docs/performance">Spanner performance docs</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>內部峰值</td>
          <td>&gt; 10 億 requests / 秒</td>
      </tr>
      <tr>
          <td>Spanner Omni 區域峰值</td>
          <td>數百萬 QPS、PB 級資料量</td>
      </tr>
      <tr>
          <td>線性擴展性</td>
          <td>2 nodes → 45000 reads/sec、4 nodes → 90000 reads/sec</td>
      </tr>
      <tr>
          <td>一致性模型</td>
          <td>external consistency（強一致 + 線性化）</td>
      </tr>
  </tbody>
</table>
<p>代表性客戶：Google 內部所有支付、廣告計費、Play 商店、Search 索引；公開客戶包括 Blockchain.com、Niantic（部分服務）、Sharechat、ZEE5、Wayfair。</p>
<p>關鍵設計：TrueTime API（GPS + 原子鐘）讓跨地區交易能維持 external consistency、不是 eventual。</p>
<h2 id="判讀">判讀</h2>
<p>Spanner 案例最值得讀的不是「能撐多大」、是「為什麼要這樣設計才能撐」。</p>
<ol>
<li><strong>線性擴展是 OLTP 的最高設計目標</strong>：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」這個 linear scaling 在傳統 OLTP（PostgreSQL、MySQL）做不到 — 因為 <em>跨節點交易</em> 需要 coordinator、coordinator 是 bottleneck。Spanner 用 Paxos + TrueTime 把 coordinator 變成「拓樸感知的多 leader」、才達成線性。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的設計取捨。</li>
<li><strong>強一致 vs 全球部署不是必須二選</strong>：CAP 定理常被解讀為「全球部署只能 eventual consistency」、Spanner 顯示「投入專屬硬體（GPS、原子鐘）+ 演算法（TrueTime）可以同時拿到 strong consistency + global distribution」。但這套硬體投資對其他 vendor 不容易複製。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的全球 OLTP 選項。</li>
<li><strong>計費粒度 = 容量規劃顆粒</strong>：Spanner 早期最小單位是 100 processing units（pu）≈ 1 node、太大讓中小負載難以用。後來推出 100 pu 起跳的 granular sizing、讓容量規劃可以從小開始。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的容量單位選擇。</li>
</ol>
<p>需要警惕：「10 億 req/sec」是 Google 內部的某個峰值瞬間、是 Spanner 服務 <em>全部使用者加總</em>、不是單一 instance 數字。讀案例時要區分「全球聚合峰值」跟「單一客戶能拿到的最大配額」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>跨地區一致性需求要在設計初期決定</strong>：如果業務必需 strong consistency（金融、ticketing）、選 Spanner 等對等服務；如果 eventual 可接受（社群、推薦）、選 Cassandra / DynamoDB Global Tables 等更便宜的選項。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的全球一致性需求識別。</li>
<li><strong>節點數即容量單位、預先規劃 sizing</strong>：Spanner 容量 = 節點數 × 單節點 QPS。每年 capacity review 主要在調節點數、不在調 schema。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>跨地區 latency 是強一致的代價</strong>：external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms。延遲敏感型業務不能用跨地區 strong consistency。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
</ol>
<p>跨平台等效：AWS Aurora DSQL（2024 推出、跨地區 strong consistency）、CockroachDB（自管）、TiDB（自管或 cloud）都是對等候選。差異是 TrueTime / 同等同步機制的成熟度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想評估全球一致性需求 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想規劃 OLTP 容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想對照其他 OLTP 案例 → <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora</a></li>
<li>想看不需要強一致的全球 KV → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth Cosmos DB</a></li>
<li>想理解 TrueTime ε 與外部一致性實作 → <a href="/blog/backend/01-database/vendors/spanner/truetime-api-depth/" data-link-title="Spanner TrueTime API 深度：GPS &#43; 原子鐘、commit wait、為什麼 line-rate scaling 才是設計目的" data-link-desc="TrueTime 是手段、line-rate scaling 才是 Spanner 的設計目的。本文先扣商業邏輯：傳統 OLTP coordinator 為什麼是 bottleneck、Spanner 怎麼用 TrueTime &#43; Paxos 換成拓樸感知多 leader；再展開 TrueTime ε / commit wait 數學、ε 暴衝失敗模式、cross-region voting 對 latency 的影響、跟 9.C10 Google internal dogfood 揭露的線性擴展模式對照">Spanner TrueTime API 深入</a></li>
<li>想對照 Spanner / Aurora DSQL / CockroachDB 不同一致性層 → <a href="/blog/backend/01-database/vendors/spanner/consistency-models-comparison/" data-link-title="Spanner Consistency Models 對照：external consistency vs serializability vs linearizability" data-link-desc="external consistency、serializability、linearizability 是三個常被混用的概念。本文先精確定義三者差異、再用 line-rate scaling 對照表（PG SSI / CockroachDB / Spanner / Aurora DSQL）回答為什麼 Spanner 不只是『更強的 serializable』、最後用 9.C10 揭露的 cross-region quorum 100-200ms 物理硬限解釋『強一致 &#43; 全球部署』的真實 cost">Spanner 一致性模型對照</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/spanner">Spanner: Always-on, virtually unlimited scale database</a></li>
<li><a href="https://cloud.google.com/spanner/docs/performance">Spanner Performance overview</a></li>
<li><a href="https://cloud.google.com/blog/products/databases/using-cloud-spanner-to-handle-high-throughput-writes/">Using Cloud Spanner to handle high throughput writes</a></li>
<li><a href="https://cloud.google.com/blog/products/databases/get-more-out-of-spanner-with-granular-instance-sizing">Get more out of Spanner with granular instance sizing</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-dsql-for-global-scale-financial-transactions/">Amazon Aurora DSQL for global-scale financial transactions</a></li>
</ul>
]]></content:encoded></item><item><title>2.C10 對照：規模差異下的快取策略</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/contrast-cache-strategy-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/contrast-cache-strategy-by-scale/</guid><description>&lt;p>這篇對照的核心責任是避免把單一快取做法視為通用解。&lt;/p>
&lt;h2 id="小型服務常見判讀">小型服務常見判讀&lt;/h2>
&lt;p>小型服務最常遇到的問題是切換時沒有先保護回源，快取架構本身夠用。用 cache-aside + TTL 完全可行，但如果沒有 warmup 與簡單限流，某次部署就可能讓熱門 key 全部 miss，直接打爆資料庫。&lt;/p>
&lt;h2 id="中型服務常見判讀">中型服務常見判讀&lt;/h2>
&lt;p>中型服務開始同時承受活動流量與版本切換壓力。這時失敗通常出在「切換順序」而不是策略名稱。先改 key 結構還是先改 TTL，會決定是否出現 stampede 連鎖反應。&lt;/p>
&lt;h2 id="大型服務常見判讀">大型服務常見判讀&lt;/h2>
&lt;p>大型服務下，快取已經是資料平面的一部分。跨區路由、分層儲存與一致性窗口會直接影響業務正確性。這個階段若只盯 hit rate，會漏掉最關鍵的資料一致性風險。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>&lt;code>origin QPS&lt;/code> 在 5 分鐘內超過基線 2 倍且持續上升&lt;/li>
&lt;li>熱門 key miss 同步上升，並伴隨重試流量增加&lt;/li>
&lt;li>stale read 比例連續惡化&lt;/li>
&lt;/ul>
&lt;p>任何一條成立就先暫停切換，回退上一個策略狀態，優先保護回源與資料一致性。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是避免把單一快取做法視為通用解。</p>
<h2 id="小型服務常見判讀">小型服務常見判讀</h2>
<p>小型服務最常遇到的問題是切換時沒有先保護回源，快取架構本身夠用。用 cache-aside + TTL 完全可行，但如果沒有 warmup 與簡單限流，某次部署就可能讓熱門 key 全部 miss，直接打爆資料庫。</p>
<h2 id="中型服務常見判讀">中型服務常見判讀</h2>
<p>中型服務開始同時承受活動流量與版本切換壓力。這時失敗通常出在「切換順序」而不是策略名稱。先改 key 結構還是先改 TTL，會決定是否出現 stampede 連鎖反應。</p>
<h2 id="大型服務常見判讀">大型服務常見判讀</h2>
<p>大型服務下，快取已經是資料平面的一部分。跨區路由、分層儲存與一致性窗口會直接影響業務正確性。這個階段若只盯 hit rate，會漏掉最關鍵的資料一致性風險。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li><code>origin QPS</code> 在 5 分鐘內超過基線 2 倍且持續上升</li>
<li>熱門 key miss 同步上升，並伴隨重試流量增加</li>
<li>stale read 比例連續惡化</li>
</ul>
<p>任何一條成立就先暫停切換，回退上一個策略狀態，優先保護回源與資料一致性。</p>
]]></content:encoded></item><item><title>3.C10 對照：規模差異下的佇列模型</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/</guid><description>&lt;p>這篇對照的核心責任是說明 queue 選型要跟著流量與組織規模改變。&lt;/p>
&lt;h2 id="小型服務常見判讀">小型服務常見判讀&lt;/h2>
&lt;p>小型服務優先用 managed queue 往往最穩，因為運維成本最低。這時候最容易忽略的是語義邊界：重試次數、死信規則、重播責任如果沒先定義，規模一上來就會出現資料重複與漏處理。&lt;/p>
&lt;h2 id="中型服務常見判讀">中型服務常見判讀&lt;/h2>
&lt;p>中型服務常見問題是 lag 與 DLQ 長期累積。根因通常是 consumer idempotency、重播流程、下游承載能力沒有一起設計，broker 效能本身很少是單點問題。&lt;/p>
&lt;h2 id="大型服務常見判讀">大型服務常見判讀&lt;/h2>
&lt;p>大型服務需要處理跨租戶與跨區壓力。此時若還用單叢集思維，任何一類流量尖峰都會拖垮整體。重點會從「怎麼送訊息」轉成「怎麼隔離失敗」。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>&lt;code>consumer lag&lt;/code> 連續超出 SLO 窗口&lt;/li>
&lt;li>&lt;code>DLQ&lt;/code> 速率上升且無法在固定時間內回收&lt;/li>
&lt;li>重播後仍出現相同失敗模式&lt;/li>
&lt;/ul>
&lt;p>出現上述條件應先凍結切換，回到前一語義設定，再逐步修正 consumer 契約與重播流程。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是說明 queue 選型要跟著流量與組織規模改變。</p>
<h2 id="小型服務常見判讀">小型服務常見判讀</h2>
<p>小型服務優先用 managed queue 往往最穩，因為運維成本最低。這時候最容易忽略的是語義邊界：重試次數、死信規則、重播責任如果沒先定義，規模一上來就會出現資料重複與漏處理。</p>
<h2 id="中型服務常見判讀">中型服務常見判讀</h2>
<p>中型服務常見問題是 lag 與 DLQ 長期累積。根因通常是 consumer idempotency、重播流程、下游承載能力沒有一起設計，broker 效能本身很少是單點問題。</p>
<h2 id="大型服務常見判讀">大型服務常見判讀</h2>
<p>大型服務需要處理跨租戶與跨區壓力。此時若還用單叢集思維，任何一類流量尖峰都會拖垮整體。重點會從「怎麼送訊息」轉成「怎麼隔離失敗」。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li><code>consumer lag</code> 連續超出 SLO 窗口</li>
<li><code>DLQ</code> 速率上升且無法在固定時間內回收</li>
<li>重播後仍出現相同失敗模式</li>
</ul>
<p>出現上述條件應先凍結切換，回到前一語義設定，再逐步修正 consumer 契約與重播流程。</p>
]]></content:encoded></item><item><title>4.C10 對照：規模差異下的觀測遷移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/</guid><description>&lt;p>這篇對照的核心責任是提醒觀測遷移是治理能力轉換，工具替換只是表面動作。&lt;/p>
&lt;h2 id="小型團隊常見判讀">小型團隊常見判讀&lt;/h2>
&lt;p>小型團隊最怕雙軌過久。若同時維護兩套儀表，通常會先耗盡人力。小團隊更需要短期對照、快速收斂，而不是一次拉滿所有治理流程。&lt;/p>
&lt;h2 id="中型團隊常見判讀">中型團隊常見判讀&lt;/h2>
&lt;p>中型團隊會碰到 schema 漂移與標籤膨脹。這個階段的失敗常見於「看得到數據，但看不懂是否同一語意」，導致告警與容量判讀彼此矛盾。&lt;/p>
&lt;h2 id="大型團隊常見判讀">大型團隊常見判讀&lt;/h2>
&lt;p>大型團隊的觀測遷移會牽涉成本分攤、採樣策略、collector 拓撲。若只追求功能對齊，往往在遷移後才出現成本暴增與告警漂移。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>新舊管線 &lt;code>error rate&lt;/code> 或 &lt;code>burn rate&lt;/code> 偏差長期超標&lt;/li>
&lt;li>missing signal 比例持續上升&lt;/li>
&lt;li>同一事件在兩套儀表板得到相反結論&lt;/li>
&lt;/ul>
&lt;p>觸發條件時應停止切換，先修資料語意與採樣策略，再決定是否繼續遷移。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;p>判讀重點是「兩套觀測是否仍在描述同一個系統狀態」。當 error rate、burn rate、trace coverage 三者任一長期偏離，就代表遷移證據不可信，應先停切換再修資料品質。&lt;/p>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這篇對照只處理觀測遷移的判讀邊界，不處理各 vendor 的實作細節。主要風險是把資料語意不一致當成短暫噪音，導致團隊在錯誤證據上推進切換。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先回到 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality&lt;/a> 修正語意與採樣，再到 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline&lt;/a> 校正雙軌管線。若已影響事故判讀，交接到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp;amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是提醒觀測遷移是治理能力轉換，工具替換只是表面動作。</p>
<h2 id="小型團隊常見判讀">小型團隊常見判讀</h2>
<p>小型團隊最怕雙軌過久。若同時維護兩套儀表，通常會先耗盡人力。小團隊更需要短期對照、快速收斂，而不是一次拉滿所有治理流程。</p>
<h2 id="中型團隊常見判讀">中型團隊常見判讀</h2>
<p>中型團隊會碰到 schema 漂移與標籤膨脹。這個階段的失敗常見於「看得到數據，但看不懂是否同一語意」，導致告警與容量判讀彼此矛盾。</p>
<h2 id="大型團隊常見判讀">大型團隊常見判讀</h2>
<p>大型團隊的觀測遷移會牽涉成本分攤、採樣策略、collector 拓撲。若只追求功能對齊，往往在遷移後才出現成本暴增與告警漂移。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li>新舊管線 <code>error rate</code> 或 <code>burn rate</code> 偏差長期超標</li>
<li>missing signal 比例持續上升</li>
<li>同一事件在兩套儀表板得到相反結論</li>
</ul>
<p>觸發條件時應停止切換，先修資料語意與採樣策略，再決定是否繼續遷移。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>判讀重點是「兩套觀測是否仍在描述同一個系統狀態」。當 error rate、burn rate、trace coverage 三者任一長期偏離，就代表遷移證據不可信，應先停切換再修資料品質。</p>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這篇對照只處理觀測遷移的判讀邊界，不處理各 vendor 的實作細節。主要風險是把資料語意不一致當成短暫噪音，導致團隊在錯誤證據上推進切換。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先回到 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a> 修正語意與採樣，再到 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a> 校正雙軌管線。若已影響事故判讀，交接到 <a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake</a>。</p>
]]></content:encoded></item><item><title>5.C10 對照：規模差異下的平台遷移</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/contrast-platform-migration-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/contrast-platform-migration-by-scale/</guid><description>&lt;p>這篇對照的核心責任是避免把同一套切流流程套到所有組織規模。遷移策略的切換單位、回退腳本化程度、依賴同步範圍與協同治理工具，在小中大型組織各有不同取捨。&lt;/p>
&lt;h2 id="小型組織常見判讀">小型組織常見判讀&lt;/h2>
&lt;p>小型組織通常能快速完成單叢集遷移，但最容易漏掉回退腳本化。結果是第一次回退就需要人工拼接操作，恢復時間不可預測。&lt;/p>
&lt;p>回退腳本化缺失的具體表現：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>手動 kubectl 操作&lt;/strong>：回退時 on-call 逐一執行 &lt;code>kubectl rollout undo&lt;/code>、手動修改 DNS 權重、手動切回 LB 規則。每一步都依賴執行者的記憶與判斷，步驟順序錯誤或遺漏都會延長恢復時間。&lt;/li>
&lt;li>&lt;strong>無 rollback script&lt;/strong>：回退流程沒有腳本化，也沒有在 staging 驗證過。第一次真正回退就是在 production 事故中。&lt;/li>
&lt;li>&lt;strong>恢復時間不可預測&lt;/strong>：手動操作的恢復時間取決於 on-call 的經驗與當下判斷力。同一個回退在不同人手上可能差 3-10 倍時間。&lt;/li>
&lt;/ul>
&lt;p>小型組織的回退投資最小可行版本是一個 shell script：按正確順序執行回退步驟、每步帶 dry-run 模式、在 staging 驗證過。這個投資的 ROI 在第一次真正回退時就回收。&lt;/p>
&lt;h2 id="中型組織常見判讀">中型組織常見判讀&lt;/h2>
&lt;p>中型組織的主要風險是依賴錯位。服務本身切過去了，但資料面、認證面、觀測面還沒同步，造成切換後局部成功、整體失敗。&lt;/p>
&lt;p>依賴錯位的常見維度：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Database endpoint&lt;/strong>：應用在新叢集但仍連舊叢集的資料庫。跨網路延遲從 &amp;lt;1ms 跳到 5-20ms，慢查詢變多、connection pool 壓力增加。嚴重時跨 AZ / region 的網路分區直接斷開連線。&lt;/li>
&lt;li>&lt;strong>Auth service&lt;/strong>：新叢集的服務用舊叢集的 auth endpoint，token 驗證走跨網路。auth 延遲增加讓每個 request 的總延遲上升，高峰時 auth 成為瓶頸。&lt;/li>
&lt;li>&lt;strong>Observability pipeline&lt;/strong>：新叢集的 metrics / logs / traces 仍送到舊叢集的收集器，或送到新收集器但 dashboard 還指向舊資料源。事故時看不到新叢集的指標，判讀盲區。&lt;/li>
&lt;li>&lt;strong>DNS 解析路徑&lt;/strong>：新叢集的 CoreDNS 設定跟舊叢集不同（upstream resolver、search domain、ndots），服務的 DNS 解析行為改變但沒被偵測到。表現為間歇性連線失敗或解析延遲。&lt;/li>
&lt;/ul>
&lt;p>中型組織的遷移 checklist 要把這四個維度列為切換前驗證項目。每個維度各自有切換時機——資料庫通常最後切（風險最高），auth 跟 observability 要先切或同步切。切換順序規劃見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移&lt;/a>。&lt;/p>
&lt;h2 id="大型組織常見判讀">大型組織常見判讀&lt;/h2>
&lt;p>大型組織的遷移失敗主要來自協同節奏失控。若沒有固定升級節奏與責任分工，單次變更容易演變成廣域事故。&lt;/p>
&lt;p>協同節奏的具體治理工具：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Upgrade calendar&lt;/strong>：所有平台級變更（叢集升級、service mesh 升級、CNI 更新）排進共用日曆。避免兩個團隊同週做影響面重疊的變更。日曆的維護者是 platform team，變更申請需提供 blast radius 估算。&lt;/li>
&lt;li>&lt;strong>Freeze window&lt;/strong>：業務高峰期（促銷、財報季、年終）凍結非緊急平台變更。freeze window 的開始 / 結束時間要明確公告，例外申請需 VP 級批准。&lt;/li>
&lt;li>&lt;strong>Blast radius estimation&lt;/strong>：每次變更前估算影響範圍——影響幾個 namespace、幾個 service、幾個使用者。估算結果進 release gate 的判定條件。工具層面可用 admission webhook 掃描變更影響的 namespace 數量。&lt;/li>
&lt;li>&lt;strong>Responsibility matrix&lt;/strong>：遷移期間的 RACI 明確化——誰負責切換、誰負責監控、誰負責回退決策、誰負責對外溝通。大型組織的遷移通常跨 3+ 團隊，責任模糊是事故升級的主要原因。&lt;/li>
&lt;/ul>
&lt;p>大型組織的平台元件升級治理見 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#%e5%b9%b3%e5%8f%b0%e5%85%83%e4%bb%b6%e5%8d%87%e7%b4%9a%e7%9a%84%e5%8f%af%e9%87%8d%e6%92%ad%e6%b5%81%e7%a8%8b" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 平台元件升級的可重播流程&lt;/a>。&lt;/p>
&lt;h2 id="跨規模的共通判讀">跨規模的共通判讀&lt;/h2>
&lt;p>三個規模的失敗模式不同（小型漏回退腳本、中型漏依賴同步、大型漏協同節奏），但共通原則是「先定回退條件再開始切換」。回退條件包含三個面向：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>觸發條件&lt;/strong>：哪些指標偏離到什麼程度就停止切換（5xx 升幅、延遲惡化、reconnect rate）。&lt;/li>
&lt;li>&lt;strong>執行路徑&lt;/strong>：回退的具體步驟、順序、負責人，且在 staging 驗證過。&lt;/li>
&lt;li>&lt;strong>完成判定&lt;/strong>：回退完成的訊號是什麼（連線數回 baseline、error rate 回 baseline、持續 N 分鐘）。&lt;/li>
&lt;/ol>
&lt;p>三個面向任一缺失，回退就會變成臨時決策——壓力下的臨時決策品質不穩定，是切流事故擴大的共通機制。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>切流批次 &lt;code>5xx&lt;/code> 異常升高&lt;/li>
&lt;li>長連線重連率飆升&lt;/li>
&lt;li>回退時間超過既定 RTO&lt;/li>
&lt;li>跨叢集依賴延遲突增（中型組織特有）&lt;/li>
&lt;/ul>
&lt;p>任一條件成立就停止下一批切換，先完成上一批穩定化與回退驗證。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移&lt;/a> 看切換順序規劃。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract&lt;/a> 看遷移後的 lifecycle 重新驗證。回 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9 反例&lt;/a> 看切流未 drain 的具體事故 timeline。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是避免把同一套切流流程套到所有組織規模。遷移策略的切換單位、回退腳本化程度、依賴同步範圍與協同治理工具，在小中大型組織各有不同取捨。</p>
<h2 id="小型組織常見判讀">小型組織常見判讀</h2>
<p>小型組織通常能快速完成單叢集遷移，但最容易漏掉回退腳本化。結果是第一次回退就需要人工拼接操作，恢復時間不可預測。</p>
<p>回退腳本化缺失的具體表現：</p>
<ul>
<li><strong>手動 kubectl 操作</strong>：回退時 on-call 逐一執行 <code>kubectl rollout undo</code>、手動修改 DNS 權重、手動切回 LB 規則。每一步都依賴執行者的記憶與判斷，步驟順序錯誤或遺漏都會延長恢復時間。</li>
<li><strong>無 rollback script</strong>：回退流程沒有腳本化，也沒有在 staging 驗證過。第一次真正回退就是在 production 事故中。</li>
<li><strong>恢復時間不可預測</strong>：手動操作的恢復時間取決於 on-call 的經驗與當下判斷力。同一個回退在不同人手上可能差 3-10 倍時間。</li>
</ul>
<p>小型組織的回退投資最小可行版本是一個 shell script：按正確順序執行回退步驟、每步帶 dry-run 模式、在 staging 驗證過。這個投資的 ROI 在第一次真正回退時就回收。</p>
<h2 id="中型組織常見判讀">中型組織常見判讀</h2>
<p>中型組織的主要風險是依賴錯位。服務本身切過去了，但資料面、認證面、觀測面還沒同步，造成切換後局部成功、整體失敗。</p>
<p>依賴錯位的常見維度：</p>
<ul>
<li><strong>Database endpoint</strong>：應用在新叢集但仍連舊叢集的資料庫。跨網路延遲從 &lt;1ms 跳到 5-20ms，慢查詢變多、connection pool 壓力增加。嚴重時跨 AZ / region 的網路分區直接斷開連線。</li>
<li><strong>Auth service</strong>：新叢集的服務用舊叢集的 auth endpoint，token 驗證走跨網路。auth 延遲增加讓每個 request 的總延遲上升，高峰時 auth 成為瓶頸。</li>
<li><strong>Observability pipeline</strong>：新叢集的 metrics / logs / traces 仍送到舊叢集的收集器，或送到新收集器但 dashboard 還指向舊資料源。事故時看不到新叢集的指標，判讀盲區。</li>
<li><strong>DNS 解析路徑</strong>：新叢集的 CoreDNS 設定跟舊叢集不同（upstream resolver、search domain、ndots），服務的 DNS 解析行為改變但沒被偵測到。表現為間歇性連線失敗或解析延遲。</li>
</ul>
<p>中型組織的遷移 checklist 要把這四個維度列為切換前驗證項目。每個維度各自有切換時機——資料庫通常最後切（風險最高），auth 跟 observability 要先切或同步切。切換順序規劃見 <a href="/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移</a>。</p>
<h2 id="大型組織常見判讀">大型組織常見判讀</h2>
<p>大型組織的遷移失敗主要來自協同節奏失控。若沒有固定升級節奏與責任分工，單次變更容易演變成廣域事故。</p>
<p>協同節奏的具體治理工具：</p>
<ul>
<li><strong>Upgrade calendar</strong>：所有平台級變更（叢集升級、service mesh 升級、CNI 更新）排進共用日曆。避免兩個團隊同週做影響面重疊的變更。日曆的維護者是 platform team，變更申請需提供 blast radius 估算。</li>
<li><strong>Freeze window</strong>：業務高峰期（促銷、財報季、年終）凍結非緊急平台變更。freeze window 的開始 / 結束時間要明確公告，例外申請需 VP 級批准。</li>
<li><strong>Blast radius estimation</strong>：每次變更前估算影響範圍——影響幾個 namespace、幾個 service、幾個使用者。估算結果進 release gate 的判定條件。工具層面可用 admission webhook 掃描變更影響的 namespace 數量。</li>
<li><strong>Responsibility matrix</strong>：遷移期間的 RACI 明確化——誰負責切換、誰負責監控、誰負責回退決策、誰負責對外溝通。大型組織的遷移通常跨 3+ 團隊，責任模糊是事故升級的主要原因。</li>
</ul>
<p>大型組織的平台元件升級治理見 <a href="/blog/backend/05-deployment-platform/traffic-config-control-plane-boundary/#%e5%b9%b3%e5%8f%b0%e5%85%83%e4%bb%b6%e5%8d%87%e7%b4%9a%e7%9a%84%e5%8f%af%e9%87%8d%e6%92%ad%e6%b5%81%e7%a8%8b" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.7 平台元件升級的可重播流程</a>。</p>
<h2 id="跨規模的共通判讀">跨規模的共通判讀</h2>
<p>三個規模的失敗模式不同（小型漏回退腳本、中型漏依賴同步、大型漏協同節奏），但共通原則是「先定回退條件再開始切換」。回退條件包含三個面向：</p>
<ol>
<li><strong>觸發條件</strong>：哪些指標偏離到什麼程度就停止切換（5xx 升幅、延遲惡化、reconnect rate）。</li>
<li><strong>執行路徑</strong>：回退的具體步驟、順序、負責人，且在 staging 驗證過。</li>
<li><strong>完成判定</strong>：回退完成的訊號是什麼（連線數回 baseline、error rate 回 baseline、持續 N 分鐘）。</li>
</ol>
<p>三個面向任一缺失，回退就會變成臨時決策——壓力下的臨時決策品質不穩定，是切流事故擴大的共通機制。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li>切流批次 <code>5xx</code> 異常升高</li>
<li>長連線重連率飆升</li>
<li>回退時間超過既定 RTO</li>
<li>跨叢集依賴延遲突增（中型組織特有）</li>
</ul>
<p>任一條件成立就停止下一批切換，先完成上一批穩定化與回退驗證。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/05-deployment-platform/kubernetes-deployment/#%e5%88%86%e9%9a%8e%e6%ae%b5%e5%b9%b3%e5%8f%b0%e9%81%b7%e7%a7%bb" data-link-title="5.2 Kubernetes 部署策略" data-link-desc="整理 deployment、probe 與 rolling update">5.2 分階段平台遷移</a> 看切換順序規劃。回 <a href="/blog/backend/05-deployment-platform/platform-lifecycle-contract/" data-link-title="5.6 Platform Lifecycle Contract" data-link-desc="說明 runtime、startup、readiness、liveness、shutdown 與 drain 如何組成平台生命週期合約。">5.6 Platform Lifecycle Contract</a> 看遷移後的 lifecycle 重新驗證。回 <a href="/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9 反例</a> 看切流未 drain 的具體事故 timeline。</p>
]]></content:encoded></item><item><title>7.C10 對照：規模差異下的身份治理</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/contrast-identity-governance-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/contrast-identity-governance-by-scale/</guid><description>&lt;p>這篇對照的核心責任是讓身份治理隨規模調整，而不是固定流程複製。&lt;/p>
&lt;h2 id="小型服務常見判讀">小型服務常見判讀&lt;/h2>
&lt;p>小型服務先把 MFA 與最小權限做好通常最有效，但常見問題是例外權限累積卻沒有回收節奏。短期看似方便，長期會形成隱性高權限風險。&lt;/p>
&lt;h2 id="中型服務常見判讀">中型服務常見判讀&lt;/h2>
&lt;p>中型服務開始出現支援系統、管理員操作、跨團隊權限交接。這時候若身份治理仍只看產品面登入流程，管理面 token 與支援流程會成為主要缺口。&lt;/p>
&lt;h2 id="大型服務常見判讀">大型服務常見判讀&lt;/h2>
&lt;p>大型服務下，身份控制面會牽涉簽章金鑰、跨租戶隔離與供應鏈責任。這個階段如果沒有分域治理與輪替節奏，故障會以控制面方式快速擴散。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>身份異常事件連續增加&lt;/li>
&lt;li>權限例外核准量超出基線且未收斂&lt;/li>
&lt;li>輪替失敗率上升並波及多系統&lt;/li>
&lt;/ul>
&lt;p>觸發條件後要先凍結高風險變更，再啟動分域輪替與例外重審，避免控制面事故擴大。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是讓身份治理隨規模調整，而不是固定流程複製。</p>
<h2 id="小型服務常見判讀">小型服務常見判讀</h2>
<p>小型服務先把 MFA 與最小權限做好通常最有效，但常見問題是例外權限累積卻沒有回收節奏。短期看似方便，長期會形成隱性高權限風險。</p>
<h2 id="中型服務常見判讀">中型服務常見判讀</h2>
<p>中型服務開始出現支援系統、管理員操作、跨團隊權限交接。這時候若身份治理仍只看產品面登入流程，管理面 token 與支援流程會成為主要缺口。</p>
<h2 id="大型服務常見判讀">大型服務常見判讀</h2>
<p>大型服務下，身份控制面會牽涉簽章金鑰、跨租戶隔離與供應鏈責任。這個階段如果沒有分域治理與輪替節奏，故障會以控制面方式快速擴散。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li>身份異常事件連續增加</li>
<li>權限例外核准量超出基線且未收斂</li>
<li>輪替失敗率上升並波及多系統</li>
</ul>
<p>觸發條件後要先凍結高風險變更，再啟動分域輪替與例外重審，避免控制面事故擴大。</p>
]]></content:encoded></item><item><title>8.10 Go 的高併發服務案例</title><link>https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/</guid><description>&lt;p>高併發服務案例的核心判斷是「大量工作是否同時存在，且每個工作都需要清楚的生命週期」。Go 適合這類服務，因為 goroutine、channel、context、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 與標準網路庫可以共同描述工作如何開始、等待、取消與清理。&lt;/p>
&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>大量 client、慢連線、斷線清理&lt;/td>
 &lt;td>Twitch、Stream、Cloudflare&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>網路代理與邊緣服務&lt;/td>
 &lt;td>timeout、連線管理、資源限制&lt;/td>
 &lt;td>Cloudflare、Kubernetes 生態工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>背景處理與 pipeline&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out&lt;/a>、排隊、取消、錯誤回報&lt;/td>
 &lt;td>PayPal、Dropbox&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分散式資料服務&lt;/td>
 &lt;td>複製、一致性、節點協調&lt;/td>
 &lt;td>Cockroach Labs&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="長連線與即時推送先看-client-是否持續留在線上">長連線與即時推送：先看 client 是否持續留在線上&lt;/h3>
&lt;p>長連線服務的核心訊號是「request 結束後，server 仍然需要替 client 保留狀態」。聊天室、直播狀態、feed 更新與即時通知，都需要管理 client 註冊、訂閱、心跳、send &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a> 與清理流程。Go 的價值在於讓每條連線的讀取、寫入與取消責任能被拆成可讀的 goroutine 流程。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/slow-client/" data-link-title="2.4 慢客戶端與 send buffer 管理" data-link-desc="控制推送佇列與記憶體風險">慢客戶端與 send buffer 管理&lt;/a>。&lt;/p>
&lt;h3 id="網路代理與邊緣服務先看邊界是否充滿-timeout">網路代理與邊緣服務：先看邊界是否充滿 timeout&lt;/h3>
&lt;p>網路代理與邊緣服務的核心訊號是「大量 I/O 邊界同時存在」。每個 request 都可能等待 DNS、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">TLS&lt;/a>、上游服務、client body 或 downstream response。Go 的 &lt;code>net/http&lt;/code>、&lt;code>context&lt;/code> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline&lt;/a> 設計讓 timeout 和 cancellation 可以沿著 request 傳遞。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/03-stdlib/http-handler/" data-link-title="3.5 net/http 與 handler 設計" data-link-desc="用 net/http 建立健康檢查、API endpoint 與清楚的 handler 邊界">net/http 與 handler 設計&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/03-stdlib/context/" data-link-title="3.7 context：取消、逾時與生命週期" data-link-desc="用 context 傳遞取消、逾時與請求生命週期">context：取消、逾時與生命週期&lt;/a>。&lt;/p>
&lt;h3 id="背景處理與-pipeline先看工作是否可以從-request-中拆出">背景處理與 pipeline：先看工作是否可以從 request 中拆出&lt;/h3>
&lt;p>背景處理的核心訊號是「使用者請求只負責提交工作，真正處理需要在後面持續執行」。例如檔案轉換、通知寄送、資料同步、報表產生與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook&lt;/a> retry。Go 的 goroutine 和 channel 可以先建立單一 process 內的 worker 模型；當工作需要跨 process 保證時，再接到 Backend 的 message &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 與 outbox 章節。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/01-concurrency-patterns/worker-pool/" data-link-title="1.5 bounded worker pool" data-link-desc="限制同時執行的 goroutine 數量，讓背景工作有明確容量邊界">bounded worker pool&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">Backend：訊息佇列與事件傳遞&lt;/a>。&lt;/p>
&lt;h3 id="分散式資料服務先看狀態是否跨節點協調">分散式資料服務：先看狀態是否跨節點協調&lt;/h3>
&lt;p>分散式資料服務的核心訊號是「資料狀態需要跨節點維持一致」。這類服務會同時處理網路延遲、節點失效、複製、leader election、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction&lt;/a> 與觀測訊號。Go 提供的是可讀的並發與錯誤處理基礎；資料庫演算法、共識協定與持久化設計則需要專門章節或外部資料補足。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/04-architecture-boundaries/source-of-truth/" data-link-title="4.3 Source of Truth：狀態邊界" data-link-desc="集中狀態更新、保護可變資料、設計查詢 projection">Source of Truth：狀態邊界&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration&lt;/a>。&lt;/p>
&lt;h2 id="案例閱讀檢查">案例閱讀檢查&lt;/h2>
&lt;p>閱讀高併發案例時，先找出三個問題：工作如何被限制數量、失敗如何回到 owner、資源如何被清理。若案例只談速度而沒有談生命週期，就很難轉成可維護的 Go 設計。&lt;/p></description><content:encoded><![CDATA[<p>高併發服務案例的核心判斷是「大量工作是否同時存在，且每個工作都需要清楚的生命週期」。Go 適合這類服務，因為 goroutine、channel、context、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 與標準網路庫可以共同描述工作如何開始、等待、取消與清理。</p>
<h2 id="高併發型態">高併發型態</h2>
<table>
  <thead>
      <tr>
          <th>型態</th>
          <th>主要壓力</th>
          <th>相關案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>長連線與即時推送</td>
          <td>大量 client、慢連線、斷線清理</td>
          <td>Twitch、Stream、Cloudflare</td>
      </tr>
      <tr>
          <td>網路代理與邊緣服務</td>
          <td>timeout、連線管理、資源限制</td>
          <td>Cloudflare、Kubernetes 生態工具</td>
      </tr>
      <tr>
          <td>背景處理與 pipeline</td>
          <td><a href="/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out</a>、排隊、取消、錯誤回報</td>
          <td>PayPal、Dropbox</td>
      </tr>
      <tr>
          <td>分散式資料服務</td>
          <td>複製、一致性、節點協調</td>
          <td>Cockroach Labs</td>
      </tr>
  </tbody>
</table>
<h3 id="長連線與即時推送先看-client-是否持續留在線上">長連線與即時推送：先看 client 是否持續留在線上</h3>
<p>長連線服務的核心訊號是「request 結束後，server 仍然需要替 client 保留狀態」。聊天室、直播狀態、feed 更新與即時通知，都需要管理 client 註冊、訂閱、心跳、send <a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a> 與清理流程。Go 的價值在於讓每條連線的讀取、寫入與取消責任能被拆成可讀的 goroutine 流程。</p>
<p>對應章節：<a href="/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構</a>、<a href="/blog/go-advanced/02-networking-websocket/slow-client/" data-link-title="2.4 慢客戶端與 send buffer 管理" data-link-desc="控制推送佇列與記憶體風險">慢客戶端與 send buffer 管理</a>。</p>
<h3 id="網路代理與邊緣服務先看邊界是否充滿-timeout">網路代理與邊緣服務：先看邊界是否充滿 timeout</h3>
<p>網路代理與邊緣服務的核心訊號是「大量 I/O 邊界同時存在」。每個 request 都可能等待 DNS、<a href="/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">TLS</a>、上游服務、client body 或 downstream response。Go 的 <code>net/http</code>、<code>context</code> 與 <a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 設計讓 timeout 和 cancellation 可以沿著 request 傳遞。</p>
<p>對應章節：<a href="/blog/go/03-stdlib/http-handler/" data-link-title="3.5 net/http 與 handler 設計" data-link-desc="用 net/http 建立健康檢查、API endpoint 與清楚的 handler 邊界">net/http 與 handler 設計</a>、<a href="/blog/go/03-stdlib/context/" data-link-title="3.7 context：取消、逾時與生命週期" data-link-desc="用 context 傳遞取消、逾時與請求生命週期">context：取消、逾時與生命週期</a>。</p>
<h3 id="背景處理與-pipeline先看工作是否可以從-request-中拆出">背景處理與 pipeline：先看工作是否可以從 request 中拆出</h3>
<p>背景處理的核心訊號是「使用者請求只負責提交工作，真正處理需要在後面持續執行」。例如檔案轉換、通知寄送、資料同步、報表產生與 <a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook</a> retry。Go 的 goroutine 和 channel 可以先建立單一 process 內的 worker 模型；當工作需要跨 process 保證時，再接到 Backend 的 message <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 與 outbox 章節。</p>
<p>對應章節：<a href="/blog/go-advanced/01-concurrency-patterns/worker-pool/" data-link-title="1.5 bounded worker pool" data-link-desc="限制同時執行的 goroutine 數量，讓背景工作有明確容量邊界">bounded worker pool</a>、<a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">Backend：訊息佇列與事件傳遞</a>。</p>
<h3 id="分散式資料服務先看狀態是否跨節點協調">分散式資料服務：先看狀態是否跨節點協調</h3>
<p>分散式資料服務的核心訊號是「資料狀態需要跨節點維持一致」。這類服務會同時處理網路延遲、節點失效、複製、leader election、<a href="/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction</a> 與觀測訊號。Go 提供的是可讀的並發與錯誤處理基礎；資料庫演算法、共識協定與持久化設計則需要專門章節或外部資料補足。</p>
<p>對應章節：<a href="/blog/go-advanced/04-architecture-boundaries/source-of-truth/" data-link-title="4.3 Source of Truth：狀態邊界" data-link-desc="集中狀態更新、保護可變資料、設計查詢 projection">Source of Truth：狀態邊界</a>、<a href="/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration</a>。</p>
<h2 id="案例閱讀檢查">案例閱讀檢查</h2>
<p>閱讀高併發案例時，先找出三個問題：工作如何被限制數量、失敗如何回到 owner、資源如何被清理。若案例只談速度而沒有談生命週期，就很難轉成可維護的 Go 設計。</p>
]]></content:encoded></item><item><title>案例研究：元編程實戰</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用元編程技術解決實際問題。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>進階技術&lt;/th>
 &lt;th>難度&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Descriptor Protocol&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Metaclass&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>Descriptor + dataclass&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>





&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">宣告式驗證（入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">自動註冊機制（進階）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">類似 Django Field（整合）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>建議先完成以下章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用元編程技術解決實際問題。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>進階技術</th>
          <th>難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證</a></td>
          <td>hook_validator.py</td>
          <td>Descriptor Protocol</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a></td>
          <td>hook_validator.py</td>
          <td>Metaclass</td>
          <td>高階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field</a></td>
          <td>hook_io.py</td>
          <td>Descriptor + dataclass</td>
          <td>高階</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>





<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">宣告式驗證（入門）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">自動註冊機制（進階）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">類似 Django Field（整合）</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></p>
]]></content:encoded></item><item><title>案例研究：非同步程式設計實戰</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用 asyncio 解決實際問題。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>進階技術&lt;/th>
 &lt;th>難度&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">非同步 subprocess&lt;/a>&lt;/td>
 &lt;td>git_utils.py&lt;/td>
 &lt;td>asyncio.create_subprocess_exec&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/parallel-io/" data-link-title="案例：並行 I/O 操作" data-link-desc="用 asyncio.gather 和 TaskGroup 實現高效的並行 I/O 操作">並行 I/O 操作&lt;/a>&lt;/td>
 &lt;td>git_utils.py&lt;/td>
 &lt;td>asyncio.gather, TaskGroup&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/" data-link-title="案例：同步/非同步橋接" data-link-desc="用 run_in_executor 和 asyncio.run 在同步與非同步程式碼之間建立橋樑">同步/非同步橋接&lt;/a>&lt;/td>
 &lt;td>整個 lib&lt;/td>
 &lt;td>run_in_executor&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>





&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">非同步 subprocess（入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">並行 I/O 操作（基礎）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">同步/非同步橋接（整合）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>建議先完成以下章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用 asyncio 解決實際問題。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>進階技術</th>
          <th>難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">非同步 subprocess</a></td>
          <td>git_utils.py</td>
          <td>asyncio.create_subprocess_exec</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/01-asyncio/case-studies/parallel-io/" data-link-title="案例：並行 I/O 操作" data-link-desc="用 asyncio.gather 和 TaskGroup 實現高效的並行 I/O 操作">並行 I/O 操作</a></td>
          <td>git_utils.py</td>
          <td>asyncio.gather, TaskGroup</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/" data-link-title="案例：同步/非同步橋接" data-link-desc="用 run_in_executor 和 asyncio.run 在同步與非同步程式碼之間建立橋樑">同步/非同步橋接</a></td>
          <td>整個 lib</td>
          <td>run_in_executor</td>
          <td>高階</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>





<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">非同步 subprocess（入門）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">並行 I/O 操作（基礎）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">同步/非同步橋接（整合）</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/fundamentals/" data-link-title="1.1 基礎概念與事件迴圈" data-link-desc="理解 asyncio 的核心概念：事件迴圈、協程與並發模型">1.1 基礎概念與事件迴圈</a></li>
<li><a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理</a></li>
<li><a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計</a></p>
]]></content:encoded></item><item><title>案例研究：效能優化實戰</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用並行處理和效能優化技術改善真實系統。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;h3 id="並行處理">並行處理&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>技術&lt;/th>
 &lt;th>預期加速&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>ThreadPoolExecutor&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>as_completed + 進度&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="效能調優">效能調優&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>技術&lt;/th>
 &lt;th>預期加速&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>re.compile&lt;/td>
 &lt;td>1.2-1.3x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取&lt;/a>&lt;/td>
 &lt;td>git_utils.py&lt;/td>
 &lt;td>functools.lru_cache&lt;/td>
 &lt;td>視命中率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>set vs list&lt;/td>
 &lt;td>10-100x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>





&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">並行檔案檢查（入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">並行 Hook 驗證（進階：含進度報告）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">正則表達式預編譯（效能調優入門）
&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">LRU 快取（快取策略）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">資料結構選擇（演算法思維）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可執行程式碼">可執行程式碼&lt;/h2>
&lt;p>所有案例都有對應的可執行程式碼：&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">&lt;span class="c1"># 下載程式碼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /path/to/your/workspace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行效能測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">python benchmarks/benchmark_parallel.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">python benchmarks/benchmark_regex.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">python benchmarks/benchmark_cache.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">python benchmarks/benchmark_data_structure.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行效能分析&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">python profiling/profile_link_checker.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>建議先完成以下章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2 效能調優實戰&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八：實戰效能優化&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用並行處理和效能優化技術改善真實系統。</p>
<h2 id="案例列表">案例列表</h2>
<h3 id="並行處理">並行處理</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>技術</th>
          <th>預期加速</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></td>
          <td>markdown_link_checker.py</td>
          <td>ThreadPoolExecutor</td>
          <td>3-5x</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></td>
          <td>hook_validator.py</td>
          <td>as_completed + 進度</td>
          <td>3-5x</td>
      </tr>
  </tbody>
</table>
<h3 id="效能調優">效能調優</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>技術</th>
          <th>預期加速</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></td>
          <td>hook_validator.py</td>
          <td>re.compile</td>
          <td>1.2-1.3x</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></td>
          <td>git_utils.py</td>
          <td>functools.lru_cache</td>
          <td>視命中率</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇</a></td>
          <td>hook_validator.py</td>
          <td>set vs list</td>
          <td>10-100x</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>





<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">並行檔案檢查（入門）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">並行 Hook 驗證（進階：含進度報告）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">正則表達式預編譯（效能調優入門）
</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">LRU 快取（快取策略）
</span></span><span class="line"><span class="ln">8</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">9</span><span class="cl">資料結構選擇（演算法思維）</span></span></code></pre></div><h2 id="可執行程式碼">可執行程式碼</h2>
<p>所有案例都有對應的可執行程式碼：</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"><span class="c1"># 下載程式碼</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nb">cd</span> /path/to/your/workspace
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 執行效能測試</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">python benchmarks/benchmark_parallel.py
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">python benchmarks/benchmark_regex.py
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">python benchmarks/benchmark_cache.py
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">python benchmarks/benchmark_data_structure.py
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 執行效能分析</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">python profiling/profile_link_checker.py</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰</a></li>
<li><a href="/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2 效能調優實戰</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八：實戰效能優化</a></p>
]]></content:encoded></item><item><title>案例研究：設計模式實戰</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用進階設計模式解決實際問題。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>進階技術&lt;/th>
 &lt;th>難度&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理&lt;/a>&lt;/td>
 &lt;td>config_loader.py&lt;/td>
 &lt;td>Context Manager&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Protocol + 註冊機制&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>異常階層 + ExceptionGroup&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Generic + TypeVar&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>





&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">快取生命週期管理（入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">異常設計架構（基礎）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">插件架構設計（進階）
&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>建議先完成以下章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1 泛型進階&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2 異常設計架構&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組三：進階設計模式&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用進階設計模式解決實際問題。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>進階技術</th>
          <th>難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></td>
          <td>config_loader.py</td>
          <td>Context Manager</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></td>
          <td>hook_validator.py</td>
          <td>Protocol + 註冊機制</td>
          <td>高階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></td>
          <td>hook_io.py</td>
          <td>異常階層 + ExceptionGroup</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></td>
          <td>hook_validator.py</td>
          <td>Generic + TypeVar</td>
          <td>高階</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>





<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">快取生命週期管理（入門）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">異常設計架構（基礎）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">插件架構設計（進階）
</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></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1 泛型進階</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2 異常設計架構</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組三：進階設計模式</a></p>
]]></content:encoded></item><item><title>4.C11 Uber：M3 大規模 Metrics 平台</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/</guid><description>&lt;p>Uber 的 M3 案例揭露了 metrics 系統從「每個團隊各跑一套 Prometheus」到「全公司共用的 metrics 平台」的轉折點。轉折的核心判斷是：當 active series 總量超過單機 Prometheus 的記憶體上限、且多個團隊需要跨叢集查詢時，自建平台層的成本低於持續橫向複製 Prometheus 實例的成本。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Uber 的服務觀測涵蓋行程追蹤、即時定價、ETA 計算、司機定位、支付結算與推播通知。每個微服務都暴露 Prometheus-compatible metrics，隨著服務數量成長到數千個，寫入速率達到每秒數十億 data points。&lt;/p>
&lt;p>早期每個團隊各自部署 Prometheus，各管自己的 retention、scrape config 與 alerting rules。規模小時這個模式運作良好 — 每個 Prometheus 實例只需要處理自己團隊的幾萬到幾十萬 series。但當組織成長到數百個團隊、數千個服務時，散落的 Prometheus 實例帶來三個問題。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="單機記憶體天花板">單機記憶體天花板&lt;/h3>
&lt;p>Prometheus 的 TSDB 把 active series 放在記憶體的 head block，每個 series 消耗約 3-4 KB（詳見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/prometheus/capacity-failure-modes/" data-link-title="Prometheus 容量規劃與故障模式" data-link-desc="說明 Prometheus 單機容量邊界、cardinality 與 retention 的資源模型、常見故障模式與判讀方式">Prometheus 容量規劃&lt;/a>）。當單一 Prometheus 實例需要 scrape 的 series 超過 1000 萬時，head block 就需要 40+ GB 記憶體。加上 query execution 跟 WAL replay 的暫時開銷，單機很容易 OOM。&lt;/p>
&lt;p>團隊的第一反應是按服務拆分多個 Prometheus 實例，但這讓跨服務查詢變得困難 — 要看一條 request 從 gateway 到 payment 的 latency 分布，需要分別查三個 Prometheus 再手動關聯。&lt;/p>
&lt;h3 id="retention-與長期趨勢">Retention 與長期趨勢&lt;/h3>
&lt;p>Prometheus 預設 retention 15 天。容量規劃與季度趨勢分析需要 90 天甚至 1 年的歷史資料。把 Prometheus retention 拉長到 90 天，disk 跟 memory 需求同步上升，而且 compaction 效率在資料量大時會下降。&lt;/p>
&lt;p>團隊需要的是分層 retention — 近期資料保留全精度、歷史資料做 downsampling 後保留更久。Prometheus 原生不支援 downsampling。&lt;/p>
&lt;h3 id="高可用與跨叢集查詢">高可用與跨叢集查詢&lt;/h3>
&lt;p>Prometheus 沒有原生 HA — 標準做法是跑兩個 instance scrape 同一批 target，靠下游去重。但兩個 instance 各自獨立儲存，查詢只打一個；instance 故障切換時會有短暫資料缺口。&lt;/p>
&lt;p>跨叢集查詢更困難。Prometheus federation 可以做簡單的 metric 聚合，但 federation 本身是 pull-based scrape — federation target 太多或 series 太大時，federation Prometheus 自己也會 OOM。&lt;/p>
&lt;h2 id="解法m3-平台">解法：M3 平台&lt;/h2>
&lt;p>Uber 開發了 M3 — 一個 Prometheus-compatible 的分散式 metrics 平台，由三個核心元件組成。&lt;/p>
&lt;h3 id="m3db分散式-time-series-storage">M3DB：分散式 time series storage&lt;/h3>
&lt;p>M3DB 是分散式 TSDB，資料按 namespace 和 shard 分布在多個節點。每個 namespace 可以有不同的 retention 和 resolution — 例如 &lt;code>realtime&lt;/code> namespace 保留 2 天全精度，&lt;code>aggregated_1m&lt;/code> namespace 保留 90 天 1 分鐘精度。這解決了 retention tiering 的問題。&lt;/p></description><content:encoded><![CDATA[<p>Uber 的 M3 案例揭露了 metrics 系統從「每個團隊各跑一套 Prometheus」到「全公司共用的 metrics 平台」的轉折點。轉折的核心判斷是：當 active series 總量超過單機 Prometheus 的記憶體上限、且多個團隊需要跨叢集查詢時，自建平台層的成本低於持續橫向複製 Prometheus 實例的成本。</p>
<h2 id="業務背景">業務背景</h2>
<p>Uber 的服務觀測涵蓋行程追蹤、即時定價、ETA 計算、司機定位、支付結算與推播通知。每個微服務都暴露 Prometheus-compatible metrics，隨著服務數量成長到數千個，寫入速率達到每秒數十億 data points。</p>
<p>早期每個團隊各自部署 Prometheus，各管自己的 retention、scrape config 與 alerting rules。規模小時這個模式運作良好 — 每個 Prometheus 實例只需要處理自己團隊的幾萬到幾十萬 series。但當組織成長到數百個團隊、數千個服務時，散落的 Prometheus 實例帶來三個問題。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="單機記憶體天花板">單機記憶體天花板</h3>
<p>Prometheus 的 TSDB 把 active series 放在記憶體的 head block，每個 series 消耗約 3-4 KB（詳見 <a href="/blog/backend/04-observability/vendors/prometheus/capacity-failure-modes/" data-link-title="Prometheus 容量規劃與故障模式" data-link-desc="說明 Prometheus 單機容量邊界、cardinality 與 retention 的資源模型、常見故障模式與判讀方式">Prometheus 容量規劃</a>）。當單一 Prometheus 實例需要 scrape 的 series 超過 1000 萬時，head block 就需要 40+ GB 記憶體。加上 query execution 跟 WAL replay 的暫時開銷，單機很容易 OOM。</p>
<p>團隊的第一反應是按服務拆分多個 Prometheus 實例，但這讓跨服務查詢變得困難 — 要看一條 request 從 gateway 到 payment 的 latency 分布，需要分別查三個 Prometheus 再手動關聯。</p>
<h3 id="retention-與長期趨勢">Retention 與長期趨勢</h3>
<p>Prometheus 預設 retention 15 天。容量規劃與季度趨勢分析需要 90 天甚至 1 年的歷史資料。把 Prometheus retention 拉長到 90 天，disk 跟 memory 需求同步上升，而且 compaction 效率在資料量大時會下降。</p>
<p>團隊需要的是分層 retention — 近期資料保留全精度、歷史資料做 downsampling 後保留更久。Prometheus 原生不支援 downsampling。</p>
<h3 id="高可用與跨叢集查詢">高可用與跨叢集查詢</h3>
<p>Prometheus 沒有原生 HA — 標準做法是跑兩個 instance scrape 同一批 target，靠下游去重。但兩個 instance 各自獨立儲存，查詢只打一個；instance 故障切換時會有短暫資料缺口。</p>
<p>跨叢集查詢更困難。Prometheus federation 可以做簡單的 metric 聚合，但 federation 本身是 pull-based scrape — federation target 太多或 series 太大時，federation Prometheus 自己也會 OOM。</p>
<h2 id="解法m3-平台">解法：M3 平台</h2>
<p>Uber 開發了 M3 — 一個 Prometheus-compatible 的分散式 metrics 平台，由三個核心元件組成。</p>
<h3 id="m3db分散式-time-series-storage">M3DB：分散式 time series storage</h3>
<p>M3DB 是分散式 TSDB，資料按 namespace 和 shard 分布在多個節點。每個 namespace 可以有不同的 retention 和 resolution — 例如 <code>realtime</code> namespace 保留 2 天全精度，<code>aggregated_1m</code> namespace 保留 90 天 1 分鐘精度。這解決了 retention tiering 的問題。</p>
<p>M3DB 的記憶體模型跟 Prometheus 不同 — 近期資料在記憶體，冷資料在 disk，不像 Prometheus 把所有 active series 都放 head block。這讓它能處理遠超單機 Prometheus 的 series 數量。</p>
<h3 id="m3-coordinator統一查詢入口">M3 Coordinator：統一查詢入口</h3>
<p>M3 Coordinator 接收 PromQL 查詢，轉譯後分發到 M3DB 節點，聚合結果後返回。對 Grafana 和 alerting rules 來說，M3 Coordinator 的 API 跟 Prometheus 完全相容 — 不需要改 dashboard 或 alert config。</p>
<h3 id="m3-aggregator寫入路徑聚合">M3 Aggregator：寫入路徑聚合</h3>
<p>高 cardinality 的原始 series 在寫入 M3DB 前先經過 M3 Aggregator 做 pre-aggregation — 例如把每秒的 request count 聚合成每分鐘，再寫入長期 namespace。這控制了長期儲存的資料量跟成本。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Prometheus standalone</th>
          <th>M3 平台</th>
          <th>Mimir / Thanos（替代）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>部署複雜度</td>
          <td>低（單一 binary）</td>
          <td>高（M3DB + Coordinator + Aggregator）</td>
          <td>中到高</td>
      </tr>
      <tr>
          <td>單機 series 上限</td>
          <td>~500 萬-1000 萬</td>
          <td>不適用（分散式）</td>
          <td>不適用</td>
      </tr>
      <tr>
          <td>Retention tiering</td>
          <td>無</td>
          <td>原生支援</td>
          <td>Thanos compactor / Mimir 支援</td>
      </tr>
      <tr>
          <td>PromQL 相容</td>
          <td>原生</td>
          <td>相容</td>
          <td>相容</td>
      </tr>
      <tr>
          <td>社群活躍度</td>
          <td>高（CNCF）</td>
          <td>低（Uber 主導、2023 後維護縮減）</td>
          <td>高（Grafana Labs / 社群）</td>
      </tr>
      <tr>
          <td>適用規模</td>
          <td>單團隊到中型組織</td>
          <td>大型組織（數十億 series）</td>
          <td>中型到大型</td>
      </tr>
  </tbody>
</table>
<p>M3 的最大風險是社群活躍度 — Uber 自 2023 年後縮減了 M3 的開發投入，Grafana Mimir 成為更活躍的替代。新專案選型時，Mimir 跟 Thanos 的社群支援度跟 Grafana 生態整合度都優於 M3。M3 的價值在於它驗證了「分散式 TSDB + 寫入路徑聚合 + retention tiering」這組設計模式，這組模式在 Mimir 跟 Thanos 裡以不同形式被採用。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2 Metrics Basics</a>：active series、cardinality 與 recording rules 的基礎模型，M3 的 pre-aggregation 對應 recording rules 的平台化版本。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：M3 的 Aggregator 是 pipeline 中 processing 層的實例。</li>
<li><a href="/blog/backend/04-observability/vendors/prometheus/remote-write-long-term-storage/" data-link-title="Remote Write 與長期儲存整合" data-link-desc="說明 Prometheus remote write 的配置、三家長期儲存後端比較（Mimir / Thanos / Cortex）、故障模式與容量規劃">Prometheus Remote Write 與長期儲存</a>：M3 是 remote write 目標之一，跟 Mimir / Thanos / Cortex 的比較在該文。</li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理</a>：M3 的 per-namespace cardinality limit 是治理機制的生產實例。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>單一 Prometheus 實例 memory 接近機器上限，開始 OOM restart</li>
<li>多個 Prometheus 實例各自 scrape，跨服務查詢需要手動關聯</li>
<li>Retention 15 天不夠做季度趨勢分析，但拉長 retention 資源撐不住</li>
<li>團隊開始問「我們的 metrics 總共有多少 series、誰佔最多」但沒有統一的 cardinality 觀測</li>
<li>Grafana federation dashboard 查詢越來越慢或經常 timeout</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.uber.com/en-GB/blog/m3/">M3: Uber&rsquo;s Open Source, Large-scale Metrics Platform for Prometheus</a></li>
</ul>
]]></content:encoded></item><item><title>7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/</guid><description>&lt;p>本案例屬於 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口治理與伺服器防護&lt;/a> 的選型比較。&lt;/p>
&lt;p>選型情境是&lt;strong>單人自用遠端 shell 存取&lt;/strong>：人在外、手機操作家中或辦公室本機的真實終端機（zsh）。兩個候選方案代表兩種根本不同的安全模型——「公開端點 + 多層防護」vs「私有網路 + 端點不存在」。&lt;/p>
&lt;h2 id="情境約束">情境約束&lt;/h2>
&lt;ul>
&lt;li>單人自用（owner = 開發 = 維運 = 唯一用戶）&lt;/li>
&lt;li>失敗代價高：整台機器的 shell 外洩&lt;/li>
&lt;li>手機端需自建 Flutter 終端機 UI（兩方案皆需）&lt;/li>
&lt;li>預算趨近零（免費方案）&lt;/li>
&lt;/ul>
&lt;h2 id="兩方案架構對比">兩方案架構對比&lt;/h2>
&lt;h3 id="方案-acloudflare-tunnel--cloudflare-access">方案 A：Cloudflare Tunnel + Cloudflare Access&lt;/h3>





&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">Flutter app（Face ID）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> │ WSS，帶三組憑證
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">Cloudflare Tunnel（named，固定網域）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">Cloudflare Access（邊緣：驗 Service Token）── 未授權流量在此被擋
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">Go proxy（本機：驗 X-App-Tunnel-Token）── 第二道
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">ttyd（本機：basic auth）── 第三道
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">zsh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="方案-btailscale-mesh-vpn">方案 B：Tailscale mesh VPN&lt;/h3>





&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">Flutter app（Face ID）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> │ WS，帶 ttyd basic auth
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">Tailscale mesh VPN（WireGuard 加密隧道，裝置級認證）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">Go proxy（本機：稽核 log + 透明轉發，不做認證）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">ttyd（本機：basic auth）── 應用層最後防線
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">zsh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="核心選型維度">核心選型維度&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Cloudflare Tunnel + Access&lt;/th>
 &lt;th>Tailscale&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>網路模型&lt;/strong>&lt;/td>
 &lt;td>出站連線到 CF 邊緣，產生&lt;strong>公開 URL&lt;/strong>&lt;/td>
 &lt;td>&lt;a href="https://www.wireguard.com/">WireGuard&lt;/a> mesh VPN，裝置間&lt;strong>私有 IP&lt;/strong>，無公開端點&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>攻擊面&lt;/strong>&lt;/td>
 &lt;td>公開 URL 存在，需層層防護&lt;/td>
 &lt;td>服務端點不存在於公開網路，攻擊者連 IP 都到不了&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>認證層數&lt;/strong>&lt;/td>
 &lt;td>三層：CF Access + proxy token + ttyd&lt;/td>
 &lt;td>兩層：Tailscale 裝置認證 + ttyd&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Go proxy 職責&lt;/strong>&lt;/td>
 &lt;td>驗 token + 稽核 log + 轉發&lt;/td>
 &lt;td>稽核 log + 轉發（不做認證）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>元件數&lt;/strong>&lt;/td>
 &lt;td>5（app → CF → CF Access → proxy → ttyd）&lt;/td>
 &lt;td>3（app → Tailscale → proxy/ttyd）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>需要自有網域&lt;/strong>&lt;/td>
 &lt;td>是&lt;/td>
 &lt;td>否（MagicDNS 自動分配）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>啟停行程數&lt;/strong>&lt;/td>
 &lt;td>3（cloudflared + ttyd + proxy）&lt;/td>
 &lt;td>2（ttyd + proxy），Tailscale daemon 常駐&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>憑證包欄位&lt;/strong>&lt;/td>
 &lt;td>8 欄（含 CF Access 憑證 + proxy token）&lt;/td>
 &lt;td>~5 欄（endpoint + ttyd 帳密）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>密鑰管理複雜度&lt;/strong>&lt;/td>
 &lt;td>高（proxy token 需可插拔後端 keychain/file/env）&lt;/td>
 &lt;td>低（僅 ttyd 帳密）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>費用&lt;/strong>&lt;/td>
 &lt;td>免費（Cloudflare 個人方案）&lt;/td>
 &lt;td>免費（Tailscale 個人方案，100 裝置內）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>外部依賴&lt;/strong>&lt;/td>
 &lt;td>Cloudflare 邊緣網路 + CF Access 控制面&lt;/td>
 &lt;td>Tailscale 協調伺服器 + DERP relay&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>供應商不可用時降級&lt;/strong>&lt;/td>
 &lt;td>邊緣不可用 = 全部連不進來；已建立連線可能存活&lt;/td>
 &lt;td>協調伺服器不可用時已建立的 WireGuard 連線存活；DERP relay 不可用只影響 NAT 穿越&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="選型判讀">選型判讀&lt;/h2>
&lt;h3 id="tailscale-勝出的場景本情境適用">Tailscale 勝出的場景（本情境適用）&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>攻擊面最小化是首要目標&lt;/strong>：shell 閘道的失敗代價極高，「端點不存在」比「保護公開端點」本質上更安全&lt;/li>
&lt;li>&lt;strong>單人自用&lt;/strong>：不需要 CF Access 的多人 policy / IdP 整合 / Device Posture 等企業功能&lt;/li>
&lt;li>&lt;strong>架構簡單性&lt;/strong>：從 5 元件 3 層認證縮為 3 元件 2 層認證，Go proxy 職責大幅簡化（砍認證閘道，只留 log + 轉發）&lt;/li>
&lt;li>&lt;strong>密鑰管理簡化&lt;/strong>：不再需要為 proxy token 建可插拔多後端（keychain/file/env），只管 ttyd 帳密&lt;/li>
&lt;li>&lt;strong>不需要自有網域&lt;/strong>：Tailscale MagicDNS 或直接用 Tailscale IP&lt;/li>
&lt;/ul>
&lt;h3 id="cloudflare-tunnel-勝出的場景本情境不適用但值得記錄">Cloudflare Tunnel 勝出的場景（本情境不適用，但值得記錄）&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>需要對外提供服務&lt;/strong>（非自用）：CF 的 WAF / CDN / rate limit / bot protection 生態豐富&lt;/li>
&lt;li>&lt;strong>需要 HTTP 層細粒度存取控制&lt;/strong>：CF Access 的 Application + Policy 模型適合管多個 internal web app&lt;/li>
&lt;li>&lt;strong>需要 Device Posture 檢查&lt;/strong>：CF 整合 CrowdStrike / SentinelOne 等 EDR 做裝置健康判斷（Device Posture：在授權前先檢查裝置的安全狀態 — 作業系統版本、磁碟加密、防毒軟體是否啟用）&lt;/li>
&lt;li>&lt;strong>已在用 Cloudflare 生態&lt;/strong>：共用控制面的管理紅利（同一 Logpush / API token / Audit Log）&lt;/li>
&lt;li>&lt;strong>多人 / 多團隊 / 合規場景&lt;/strong>：CF Access 的 IdP 整合 + Service Auth + Audit Log 比 Tailscale 個人方案完整&lt;/li>
&lt;/ul>
&lt;h3 id="邊界情境">邊界情境&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>多人但仍小規模&lt;/strong>（2-5 人）：Tailscale ACL（存取控制清單，定義哪些裝置可存取哪些服務）足以控制；超過此規模再評估 CF Access 或 Teleport&lt;/li>
&lt;li>&lt;strong>需要 session recording&lt;/strong>：兩者都沒有一流方案——Tailscale 需 Enterprise tier，CF Access 只記 metadata 不錄 keystroke。重 audit 走 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/vendors/teleport/" data-link-title="Teleport" data-link-desc="Identity-Aware Proxy &amp;#43; PAM、SSH / DB / K8s / Desktop session 統一 short-lived cert &amp;#43; session recording &amp;#43; JIT、跟 Okta / Vault 互補">Teleport&lt;/a>&lt;/li>
&lt;li>&lt;strong>需要從固定 IP 出網&lt;/strong>：Tailscale Exit Node 可做但不是設計核心；CF 有更成熟的方案&lt;/li>
&lt;/ul>
&lt;h2 id="tailscale-採用後的安全底線">Tailscale 採用後的安全底線&lt;/h2>
&lt;p>即使 Tailscale 攻擊面更小，仍需維持以下底線：&lt;/p></description><content:encoded><![CDATA[<p>本案例屬於 <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口治理與伺服器防護</a> 的選型比較。</p>
<p>選型情境是<strong>單人自用遠端 shell 存取</strong>：人在外、手機操作家中或辦公室本機的真實終端機（zsh）。兩個候選方案代表兩種根本不同的安全模型——「公開端點 + 多層防護」vs「私有網路 + 端點不存在」。</p>
<h2 id="情境約束">情境約束</h2>
<ul>
<li>單人自用（owner = 開發 = 維運 = 唯一用戶）</li>
<li>失敗代價高：整台機器的 shell 外洩</li>
<li>手機端需自建 Flutter 終端機 UI（兩方案皆需）</li>
<li>預算趨近零（免費方案）</li>
</ul>
<h2 id="兩方案架構對比">兩方案架構對比</h2>
<h3 id="方案-acloudflare-tunnel--cloudflare-access">方案 A：Cloudflare Tunnel + Cloudflare Access</h3>





<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">Flutter app（Face ID）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   │  WSS，帶三組憑證
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Cloudflare Tunnel（named，固定網域）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Cloudflare Access（邊緣：驗 Service Token）── 未授權流量在此被擋
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">Go proxy（本機：驗 X-App-Tunnel-Token）── 第二道
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln">10</span><span class="cl">ttyd（本機：basic auth）── 第三道
</span></span><span class="line"><span class="ln">11</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln">12</span><span class="cl">zsh</span></span></code></pre></div><h3 id="方案-btailscale-mesh-vpn">方案 B：Tailscale mesh VPN</h3>





<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">Flutter app（Face ID）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   │  WS，帶 ttyd basic auth
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">Tailscale mesh VPN（WireGuard 加密隧道，裝置級認證）
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">Go proxy（本機：稽核 log + 透明轉發，不做認證）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">ttyd（本機：basic auth）── 應用層最後防線
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   ▼
</span></span><span class="line"><span class="ln">10</span><span class="cl">zsh</span></span></code></pre></div><h2 id="核心選型維度">核心選型維度</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Cloudflare Tunnel + Access</th>
          <th>Tailscale</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>網路模型</strong></td>
          <td>出站連線到 CF 邊緣，產生<strong>公開 URL</strong></td>
          <td><a href="https://www.wireguard.com/">WireGuard</a> mesh VPN，裝置間<strong>私有 IP</strong>，無公開端點</td>
      </tr>
      <tr>
          <td><strong>攻擊面</strong></td>
          <td>公開 URL 存在，需層層防護</td>
          <td>服務端點不存在於公開網路，攻擊者連 IP 都到不了</td>
      </tr>
      <tr>
          <td><strong>認證層數</strong></td>
          <td>三層：CF Access + proxy token + ttyd</td>
          <td>兩層：Tailscale 裝置認證 + ttyd</td>
      </tr>
      <tr>
          <td><strong>Go proxy 職責</strong></td>
          <td>驗 token + 稽核 log + 轉發</td>
          <td>稽核 log + 轉發（不做認證）</td>
      </tr>
      <tr>
          <td><strong>元件數</strong></td>
          <td>5（app → CF → CF Access → proxy → ttyd）</td>
          <td>3（app → Tailscale → proxy/ttyd）</td>
      </tr>
      <tr>
          <td><strong>需要自有網域</strong></td>
          <td>是</td>
          <td>否（MagicDNS 自動分配）</td>
      </tr>
      <tr>
          <td><strong>啟停行程數</strong></td>
          <td>3（cloudflared + ttyd + proxy）</td>
          <td>2（ttyd + proxy），Tailscale daemon 常駐</td>
      </tr>
      <tr>
          <td><strong>憑證包欄位</strong></td>
          <td>8 欄（含 CF Access 憑證 + proxy token）</td>
          <td>~5 欄（endpoint + ttyd 帳密）</td>
      </tr>
      <tr>
          <td><strong>密鑰管理複雜度</strong></td>
          <td>高（proxy token 需可插拔後端 keychain/file/env）</td>
          <td>低（僅 ttyd 帳密）</td>
      </tr>
      <tr>
          <td><strong>費用</strong></td>
          <td>免費（Cloudflare 個人方案）</td>
          <td>免費（Tailscale 個人方案，100 裝置內）</td>
      </tr>
      <tr>
          <td><strong>外部依賴</strong></td>
          <td>Cloudflare 邊緣網路 + CF Access 控制面</td>
          <td>Tailscale 協調伺服器 + DERP relay</td>
      </tr>
      <tr>
          <td><strong>供應商不可用時降級</strong></td>
          <td>邊緣不可用 = 全部連不進來；已建立連線可能存活</td>
          <td>協調伺服器不可用時已建立的 WireGuard 連線存活；DERP relay 不可用只影響 NAT 穿越</td>
      </tr>
  </tbody>
</table>
<h2 id="選型判讀">選型判讀</h2>
<h3 id="tailscale-勝出的場景本情境適用">Tailscale 勝出的場景（本情境適用）</h3>
<ul>
<li><strong>攻擊面最小化是首要目標</strong>：shell 閘道的失敗代價極高，「端點不存在」比「保護公開端點」本質上更安全</li>
<li><strong>單人自用</strong>：不需要 CF Access 的多人 policy / IdP 整合 / Device Posture 等企業功能</li>
<li><strong>架構簡單性</strong>：從 5 元件 3 層認證縮為 3 元件 2 層認證，Go proxy 職責大幅簡化（砍認證閘道，只留 log + 轉發）</li>
<li><strong>密鑰管理簡化</strong>：不再需要為 proxy token 建可插拔多後端（keychain/file/env），只管 ttyd 帳密</li>
<li><strong>不需要自有網域</strong>：Tailscale MagicDNS 或直接用 Tailscale IP</li>
</ul>
<h3 id="cloudflare-tunnel-勝出的場景本情境不適用但值得記錄">Cloudflare Tunnel 勝出的場景（本情境不適用，但值得記錄）</h3>
<ul>
<li><strong>需要對外提供服務</strong>（非自用）：CF 的 WAF / CDN / rate limit / bot protection 生態豐富</li>
<li><strong>需要 HTTP 層細粒度存取控制</strong>：CF Access 的 Application + Policy 模型適合管多個 internal web app</li>
<li><strong>需要 Device Posture 檢查</strong>：CF 整合 CrowdStrike / SentinelOne 等 EDR 做裝置健康判斷（Device Posture：在授權前先檢查裝置的安全狀態 — 作業系統版本、磁碟加密、防毒軟體是否啟用）</li>
<li><strong>已在用 Cloudflare 生態</strong>：共用控制面的管理紅利（同一 Logpush / API token / Audit Log）</li>
<li><strong>多人 / 多團隊 / 合規場景</strong>：CF Access 的 IdP 整合 + Service Auth + Audit Log 比 Tailscale 個人方案完整</li>
</ul>
<h3 id="邊界情境">邊界情境</h3>
<ul>
<li><strong>多人但仍小規模</strong>（2-5 人）：Tailscale ACL（存取控制清單，定義哪些裝置可存取哪些服務）足以控制；超過此規模再評估 CF Access 或 Teleport</li>
<li><strong>需要 session recording</strong>：兩者都沒有一流方案——Tailscale 需 Enterprise tier，CF Access 只記 metadata 不錄 keystroke。重 audit 走 <a href="/blog/backend/07-security-data-protection/vendors/teleport/" data-link-title="Teleport" data-link-desc="Identity-Aware Proxy &#43; PAM、SSH / DB / K8s / Desktop session 統一 short-lived cert &#43; session recording &#43; JIT、跟 Okta / Vault 互補">Teleport</a></li>
<li><strong>需要從固定 IP 出網</strong>：Tailscale Exit Node 可做但不是設計核心；CF 有更成熟的方案</li>
</ul>
<h2 id="tailscale-採用後的安全底線">Tailscale 採用後的安全底線</h2>
<p>即使 Tailscale 攻擊面更小，仍需維持以下底線：</p>
<table>
  <thead>
      <tr>
          <th>底線</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ttyd 綁 Tailscale 介面或 localhost</td>
          <td>不監聽公開網路介面</td>
      </tr>
      <tr>
          <td>Tailscale ACL 限制裝置</td>
          <td>只有 owner 裝置可存取 proxy port</td>
      </tr>
      <tr>
          <td>ttyd basic auth</td>
          <td>Tailscale 萬一被穿越的最後防線</td>
      </tr>
      <tr>
          <td>稽核 log</td>
          <td>proxy 記錄每次連線（client_ip，不含 PTY 內容）</td>
      </tr>
      <tr>
          <td>不開機自啟（ttyd/proxy）</td>
          <td>手動起停最小化服務暴露窗</td>
      </tr>
  </tbody>
</table>
<h2 id="此選型的-tripwire">此選型的 tripwire</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>觸發後重評</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>從單人變多人</td>
          <td>Tailscale ACL 是否足夠，或需升級為 Teleport / CF Access</td>
      </tr>
      <tr>
          <td>需要對外暴露服務</td>
          <td>Tailscale Funnel 不適合 production hardened ingress，改走 CF</td>
      </tr>
      <tr>
          <td>需要合規 session recording</td>
          <td>Tailscale Enterprise 或改走 Teleport</td>
      </tr>
      <tr>
          <td>需要 WAF / bot protection</td>
          <td>Tailscale 沒有應用層防護，改走 CF</td>
      </tr>
      <tr>
          <td>Tailscale key 即將到期</td>
          <td>確認 key expiry 政策（預設 180 天）、設提醒避免裝置靜默掉線</td>
      </tr>
  </tbody>
</table>
<h2 id="從本情境到-vendor-詳頁">從本情境到 vendor 詳頁</h2>
<ul>
<li>Tailscale 完整 vendor 判讀 → <a href="/blog/backend/07-security-data-protection/vendors/tailscale-ssh/" data-link-title="Tailscale SSH" data-link-desc="WireGuard-based zero-trust mesh &#43; identity-bound SSH、ACL JSON policy、developer-friendly、跟 IdP integration 取代 SSH key">Tailscale SSH</a></li>
<li>Cloudflare Access 完整 vendor 判讀 → <a href="/blog/backend/07-security-data-protection/vendors/cloudflare-access/" data-link-title="Cloudflare Access" data-link-desc="Zero Trust Network Access (ZTNA)、取代 VPN 的 application-layer access、Argo Tunnel &#43; Device Posture &#43; IdP integration">Cloudflare Access</a></li>
<li>Infrastructure access + 合規場景 → <a href="/blog/backend/07-security-data-protection/vendors/teleport/" data-link-title="Teleport" data-link-desc="Identity-Aware Proxy &#43; PAM、SSH / DB / K8s / Desktop session 統一 short-lived cert &#43; session recording &#43; JIT、跟 Okta / Vault 互補">Teleport</a></li>
<li>本選型的章節歸屬 → <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口治理與伺服器防護</a></li>
</ul>
]]></content:encoded></item><item><title>3.C11 Pinterest：Kafka tiered storage broker-decoupled</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/</guid><description>&lt;p>這個案例的核心責任是說明 tiered storage 不只是「冷資料 offload」、是 broker 與儲存解耦的架構選擇。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pinterest 從 Kafka broker 卸 ~200 TB/day 熱資料到 S3、2024 年 5 月起 20+ production topic 上線、跟 KIP-405 native tiered storage 不同、採 broker-decoupled 設計。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Broker-decoupled 設計讓 consumer 直接從 S3 拉、broker 不再是熱路徑。揭露「broker resource 跟 cross-AZ network cost」其實該分離治理、而非綁在 broker 容量擴張上。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：tiered storage / 跨層儲存成本。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/pinterest-tiered-storage-for-apache-kafka-%EF%B8%8F-a-broker-decoupled-approach-c33c69e9958b">Pinterest Tiered Storage for Apache Kafka — a Broker-Decoupled Approach&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 tiered storage 不只是「冷資料 offload」、是 broker 與儲存解耦的架構選擇。</p>
<h2 id="觀察">觀察</h2>
<p>Pinterest 從 Kafka broker 卸 ~200 TB/day 熱資料到 S3、2024 年 5 月起 20+ production topic 上線、跟 KIP-405 native tiered storage 不同、採 broker-decoupled 設計。</p>
<h2 id="判讀">判讀</h2>
<p>Broker-decoupled 設計讓 consumer 直接從 S3 拉、broker 不再是熱路徑。揭露「broker resource 跟 cross-AZ network cost」其實該分離治理、而非綁在 broker 容量擴張上。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：tiered storage / 跨層儲存成本。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/pinterest-tiered-storage-for-apache-kafka-%EF%B8%8F-a-broker-decoupled-approach-c33c69e9958b">Pinterest Tiered Storage for Apache Kafka — a Broker-Decoupled Approach</a></li>
</ul>
]]></content:encoded></item><item><title>9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/</guid><description>&lt;p>這個案例的核心責任是說明「全球分散式 multi-model DB」的容量設計取捨。Minecraft Earth 是 AR 手機遊戲（已停運、但案例本身保留）、跟 Pokémon GO 同類負載 — 玩家位置即時更新、跨地區即時互動、預期會在熱門地區 surge。Cosmos DB 的設計回應這類「跨地區 + 多 model」需求。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Minecraft Earth 在 Azure Cosmos DB 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB&lt;/a>）：&lt;/p>
&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>100 萬 RU/s（Request Units / 秒）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲承諾&lt;/td>
 &lt;td>99 百分位 &amp;lt; 10ms（地區內讀）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一致性選項&lt;/td>
 &lt;td>5 個一致性層級（strong → eventual）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>地理分散&lt;/td>
 &lt;td>turnkey global distribution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性 SLA&lt;/td>
 &lt;td>99.99%（multi-region 99.999%）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Cosmos DB 平台特性（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">Cosmos DB technical overview&lt;/a>）：&lt;/p>
&lt;ul>
&lt;li>配置擴容延遲：99 百分位 5 秒內生效&lt;/li>
&lt;li>多 model 支援：SQL API、MongoDB API、Cassandra API、Gremlin、Table&lt;/li>
&lt;li>partition 動態分裂：透明&lt;/li>
&lt;li>5 個 well-defined consistency levels（strong / bounded staleness / session / consistent prefix / eventual）&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Cosmos DB 設計揭露三個全球 KV / document DB 的容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>一致性是 spectrum、不是 binary&lt;/strong>：Cosmos DB 提供 5 個層級、每個延遲與吞吐特性不同。AR 遊戲的玩家位置不需要 strong consistency（位置稍微 stale 沒問題）、但庫存交易需要 strong。同一 application 內不同操作選不同 consistency、是進階的容量設計策略。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的一致性取捨。&lt;/li>
&lt;li>&lt;strong>Request Unit (RU) 是抽象容量單位&lt;/strong>：1 RU = 1 KB document 的 strong read 成本、寫成本約 5 RU、複雜 query 可達數百 RU。容量規劃變成「估每個操作多少 RU × 操作頻率」、跟「估 CPU / IOPS」是不同的思維。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的容量單位設計。&lt;/li>
&lt;li>&lt;strong>turnkey global distribution = 容量單位的全球複製&lt;/strong>：開啟跨地區後、容量在每個地區都 mirror 一份、成本乘以地區數。對中等規模團隊、turnkey 省下大量 ops、但要算「全球複製的成本是否值得業務需求」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「100 萬 RU/s 通過測試」是 &lt;em>壓測通過&lt;/em>、不是 &lt;em>生產持續跑&lt;/em>。實際營運要看 partition key 設計是否均勻、是否有 hot partition、跨地區複製延遲是否符合業務需求。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>一致性需求分流到不同 collection / table&lt;/strong>：同一 application 不同操作有不同一致性需求、用不同 collection 配不同 consistency level、不要一刀切。&lt;/li>
&lt;li>&lt;strong>partition key 設計影響容量上限&lt;/strong>：跟 DynamoDB 一樣、hot partition 會讓名義容量達不到。Cosmos DB 的特殊性是「synthetic partition key」可以混合多個 field 強制分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;li>&lt;strong>RU-based pricing 鼓勵 query 最佳化&lt;/strong>：每個 expensive query 都吃 RU、優化 query 直接降成本。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop&lt;/a> 的持續改進迴圈。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS DynamoDB Global Tables（global KV）、GCP Spanner（global SQL with strong consistency）、ScyllaDB Cloud（自管 Cassandra）都是對等候選。差異是 multi-model 廣度（Cosmos 最廣）vs 一致性深度（Spanner 最強）。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「全球分散式 multi-model DB」的容量設計取捨。Minecraft Earth 是 AR 手機遊戲（已停運、但案例本身保留）、跟 Pokémon GO 同類負載 — 玩家位置即時更新、跨地區即時互動、預期會在熱門地區 surge。Cosmos DB 的設計回應這類「跨地區 + 多 model」需求。</p>
<h2 id="觀察">觀察</h2>
<p>Minecraft Earth 在 Azure Cosmos DB 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字 / 內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>容量測試</td>
          <td>100 萬 RU/s（Request Units / 秒）</td>
      </tr>
      <tr>
          <td>延遲承諾</td>
          <td>99 百分位 &lt; 10ms（地區內讀）</td>
      </tr>
      <tr>
          <td>一致性選項</td>
          <td>5 個一致性層級（strong → eventual）</td>
      </tr>
      <tr>
          <td>地理分散</td>
          <td>turnkey global distribution</td>
      </tr>
      <tr>
          <td>可用性 SLA</td>
          <td>99.99%（multi-region 99.999%）</td>
      </tr>
  </tbody>
</table>
<p>Cosmos DB 平台特性（引自 <a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">Cosmos DB technical overview</a>）：</p>
<ul>
<li>配置擴容延遲：99 百分位 5 秒內生效</li>
<li>多 model 支援：SQL API、MongoDB API、Cassandra API、Gremlin、Table</li>
<li>partition 動態分裂：透明</li>
<li>5 個 well-defined consistency levels（strong / bounded staleness / session / consistent prefix / eventual）</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Cosmos DB 設計揭露三個全球 KV / document DB 的容量設計重點。</p>
<ol>
<li><strong>一致性是 spectrum、不是 binary</strong>：Cosmos DB 提供 5 個層級、每個延遲與吞吐特性不同。AR 遊戲的玩家位置不需要 strong consistency（位置稍微 stale 沒問題）、但庫存交易需要 strong。同一 application 內不同操作選不同 consistency、是進階的容量設計策略。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>Request Unit (RU) 是抽象容量單位</strong>：1 RU = 1 KB document 的 strong read 成本、寫成本約 5 RU、複雜 query 可達數百 RU。容量規劃變成「估每個操作多少 RU × 操作頻率」、跟「估 CPU / IOPS」是不同的思維。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的容量單位設計。</li>
<li><strong>turnkey global distribution = 容量單位的全球複製</strong>：開啟跨地區後、容量在每個地區都 mirror 一份、成本乘以地區數。對中等規模團隊、turnkey 省下大量 ops、但要算「全球複製的成本是否值得業務需求」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>需要警惕：「100 萬 RU/s 通過測試」是 <em>壓測通過</em>、不是 <em>生產持續跑</em>。實際營運要看 partition key 設計是否均勻、是否有 hot partition、跨地區複製延遲是否符合業務需求。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>一致性需求分流到不同 collection / table</strong>：同一 application 不同操作有不同一致性需求、用不同 collection 配不同 consistency level、不要一刀切。</li>
<li><strong>partition key 設計影響容量上限</strong>：跟 DynamoDB 一樣、hot partition 會讓名義容量達不到。Cosmos DB 的特殊性是「synthetic partition key」可以混合多個 field 強制分散。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
<li><strong>RU-based pricing 鼓勵 query 最佳化</strong>：每個 expensive query 都吃 RU、優化 query 直接降成本。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> 的持續改進迴圈。</li>
</ol>
<p>跨平台等效：AWS DynamoDB Global Tables（global KV）、GCP Spanner（global SQL with strong consistency）、ScyllaDB Cloud（自管 Cassandra）都是對等候選。差異是 multi-model 廣度（Cosmos 最廣）vs 一致性深度（Spanner 最強）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計全球分散 KV → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想對照強一致全球 OLTP → <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想對照單區 KV 高吞吐 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB</a></li>
<li>想理解 consistency level 的取捨 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想理解 Cosmos DB 五層一致性的工程選擇 → <a href="/blog/backend/01-database/vendors/cosmosdb/consistency-levels-engineering/" data-link-title="Cosmos DB 5 Consistency Levels：Session 預設、Bounded staleness、Strong 邊界跟跨 collection 分流策略" data-link-desc="Cosmos DB 5 個 consistency level 的工程選擇邏輯、Session 為何是 production 預設、per-request override 跟跨 collection 分流的進階策略、Strong &#43; multi-region 互斥的 cross-link — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB 一致性層次工程</a></li>
<li>想做全球 multi-region write 衝突收斂 → <a href="/blog/backend/01-database/vendors/cosmosdb/multi-region-write-conflict/" data-link-title="Cosmos DB Multi-Region Write：active-active、LWW、custom merge、Strong &#43; multi-region 互斥的 AP 取捨" data-link-desc="Multi-region active-active write 的 conflict resolution（LWW / custom merge / conflict feed）、Strong 跟 multi-region write 為什麼互斥、廣告 SLA vs 實測可用性鏈路拆解 — 從 Minecraft Earth &#43; Toyota Connected 切入">Cosmos DB 多 region write 衝突</a></li>
<li>想拆 partition key 設計與全球分散搭配 → <a href="/blog/backend/01-database/vendors/cosmosdb/partition-key-design/" data-link-title="Cosmos DB Partition Key Design：synthetic / composite / hierarchical &#43; 不可逆性硬約束" data-link-desc="Cosmos DB logical partition 10000 RU/s 上限、partition key 不可改、三種設計模式（synthetic / composite / hierarchical）、跟 DynamoDB / MongoDB 可逆性對比、latency budget 拆解 — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB partition key 設計</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB part 2: Delivering turnkey geographic distribution</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">A technical overview of Azure Cosmos DB</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/azure-cosmos-db-pushing-the-frontier-of-globally-distributed-databases/">Azure Cosmos DB: Pushing the frontier of globally distributed databases</a></li>
</ul>
]]></content:encoded></item><item><title>Google：Error Budget 政策如何決定發布節奏</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/</guid><description>&lt;p>Error budget policy 的核心責任是把「可靠性目標」轉成「發布節奏控制」。團隊不需要在每次風險升高時重新爭論要不要繼續推版，而是用同一套 SLO 消耗判準決定放行、限流或凍結。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>高變更頻率服務最常見的失效是小幅回歸連續累積，單點故障反而少見。每次回歸都不夠大，不會立刻觸發全停；但連續幾週後，使用者體感持續惡化，團隊才發現可靠性債已經超標。&lt;/p>
&lt;p>這種情境需要的是「連續消耗判讀」，不是單次事故判讀。error budget policy 就是把連續消耗變成可操作的放行規則。&lt;/p>
&lt;h2 id="決策機制">決策機制&lt;/h2>
&lt;p>政策設計先做三個對齊，再做門檻定義。&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>使用者行為對齊&lt;/td>
 &lt;td>哪些 journey 直接反映服務價值&lt;/td>
 &lt;td>SLI 範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可靠性承諾對齊&lt;/td>
 &lt;td>什麼水準算服務仍可接受&lt;/td>
 &lt;td>SLO 目標&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>交付節奏對齊&lt;/td>
 &lt;td>可靠性消耗到哪裡要改變發布策略&lt;/td>
 &lt;td>Budget gate&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>有了這三個對齊後，release gate 可以從「主觀風險判斷」轉成「政策驅動」：&lt;/p>
&lt;ol>
&lt;li>budget 健康：正常發版。&lt;/li>
&lt;li>budget 快速消耗：啟用變更限速、提高驗證門檻。&lt;/li>
&lt;li>budget 透支：凍結非必要變更，先修復與回補訊號。&lt;/li>
&lt;/ol>
&lt;h2 id="可觀測訊號">可觀測訊號&lt;/h2>
&lt;p>政策有效與否要靠訊號判讀，不靠會議共識。&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>burn rate&lt;/td>
 &lt;td>是否進入短期高消耗區&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>release failure ratio&lt;/td>
 &lt;td>發版後回歸是否集中&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>alert noise&lt;/td>
 &lt;td>告警是否支持 gate 判讀&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>recovery latency&lt;/td>
 &lt;td>凍結後修復是否收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把 error budget 當 KPI 會讓政策失真。這個機制的責任是「保護可靠性與交付節奏的平衡」，不是讓團隊追求某個固定分數。當 KPI 化開始主導行為，常見結果是 SLI 縮小、告警延後或例外條件過度擴張，最終反而降低判讀可信度。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>要把這個案例落到制度層，先回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6&lt;/a> 定義政策欄位，再到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a> 實作 gate。若你發現訊號不足，先補 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">4.16&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Error budget policy 的核心責任是把「可靠性目標」轉成「發布節奏控制」。團隊不需要在每次風險升高時重新爭論要不要繼續推版，而是用同一套 SLO 消耗判準決定放行、限流或凍結。</p>
<h2 id="問題場景">問題場景</h2>
<p>高變更頻率服務最常見的失效是小幅回歸連續累積，單點故障反而少見。每次回歸都不夠大，不會立刻觸發全停；但連續幾週後，使用者體感持續惡化，團隊才發現可靠性債已經超標。</p>
<p>這種情境需要的是「連續消耗判讀」，不是單次事故判讀。error budget policy 就是把連續消耗變成可操作的放行規則。</p>
<h2 id="決策機制">決策機制</h2>
<p>政策設計先做三個對齊，再做門檻定義。</p>
<table>
  <thead>
      <tr>
          <th>對齊項目</th>
          <th>核心問題</th>
          <th>產出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者行為對齊</td>
          <td>哪些 journey 直接反映服務價值</td>
          <td>SLI 範圍</td>
      </tr>
      <tr>
          <td>可靠性承諾對齊</td>
          <td>什麼水準算服務仍可接受</td>
          <td>SLO 目標</td>
      </tr>
      <tr>
          <td>交付節奏對齊</td>
          <td>可靠性消耗到哪裡要改變發布策略</td>
          <td>Budget gate</td>
      </tr>
  </tbody>
</table>
<p>有了這三個對齊後，release gate 可以從「主觀風險判斷」轉成「政策驅動」：</p>
<ol>
<li>budget 健康：正常發版。</li>
<li>budget 快速消耗：啟用變更限速、提高驗證門檻。</li>
<li>budget 透支：凍結非必要變更，先修復與回補訊號。</li>
</ol>
<h2 id="可觀測訊號">可觀測訊號</h2>
<p>政策有效與否要靠訊號判讀，不靠會議共識。</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>burn rate</td>
          <td>是否進入短期高消耗區</td>
          <td><a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6</a></td>
      </tr>
      <tr>
          <td>release failure ratio</td>
          <td>發版後回歸是否集中</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>alert noise</td>
          <td>告警是否支持 gate 判讀</td>
          <td><a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6</a></td>
      </tr>
      <tr>
          <td>recovery latency</td>
          <td>凍結後修復是否收斂</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把 error budget 當 KPI 會讓政策失真。這個機制的責任是「保護可靠性與交付節奏的平衡」，不是讓團隊追求某個固定分數。當 KPI 化開始主導行為，常見結果是 SLI 縮小、告警延後或例外條件過度擴張，最終反而降低判讀可信度。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>要把這個案例落到制度層，先回到 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6</a> 定義政策欄位，再到 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a> 實作 gate。若你發現訊號不足，先補 <a href="/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">4.16</a> 與 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20</a>。</p>
]]></content:encoded></item><item><title>Slack：2022 連線恢復與狀態通訊節奏</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/</guid><description>&lt;p>這起案例的核心責任是維持「恢復動作」與「外部通訊」同步。對通訊平台來說，狀態揭露本身就是事故處理的一級控制面。&lt;/p>
&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>reconnect spike&lt;/td>
 &lt;td>回復是否造成新一輪壓力&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>status update cadence&lt;/td>
 &lt;td>對外節奏是否穩定&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>workspace impact spread&lt;/td>
 &lt;td>影響是否跨租戶擴散&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「連線恢復節奏」與「對外通訊節奏」必須同步。主要風險是恢復動作先行但通訊滯後，造成客戶端行為與狀態頁資訊脫節。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先保住連線層穩態，再做狀態同步。事故後把通訊節奏與指揮欄位回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是維持「恢復動作」與「外部通訊」同步。對通訊平台來說，狀態揭露本身就是事故處理的一級控制面。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>reconnect spike</td>
          <td>回復是否造成新一輪壓力</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>status update cadence</td>
          <td>對外節奏是否穩定</td>
          <td><a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4</a></td>
      </tr>
      <tr>
          <td>workspace impact spread</td>
          <td>影響是否跨租戶擴散</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「連線恢復節奏」與「對外通訊節奏」必須同步。主要風險是恢復動作先行但通訊滯後，造成客戶端行為與狀態頁資訊脫節。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先保住連線層穩態，再做狀態同步。事故後把通訊節奏與指揮欄位回寫 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a> 與 <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4</a>。</p>
]]></content:encoded></item><item><title>LinkedIn</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/</guid><description>&lt;p>LinkedIn 是大規模社交平台、capacity planning 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 結構的工程文章公開度高、是「中型公司如何規模化 SRE」的教學標竿。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Capacity Planning：跨 region / 跨服務的容量預測方法&lt;/li>
&lt;li>On-call 結構：primary / secondary / SME escalation&lt;/li>
&lt;li>Operability culture：把可運維性納入服務設計門檻&lt;/li>
&lt;li>Internal tooling：LinkedIn engineering blog 公開的內部工具設計&lt;/li>
&lt;/ul>
&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>Capacity Planning&lt;/td>
 &lt;td>預測模型、headroom、growth rate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>On-call Tiers&lt;/td>
 &lt;td>多層 escalation 設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Site Reliability Eng&lt;/td>
 &lt;td>LinkedIn SRE 組織演化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Internal Chaos / Drills&lt;/td>
 &lt;td>Project Waterbear 等內部演練&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>LinkedIn 這個案例在講的是中大型平台如何把容量規劃、自動化壓測與 metrics 收集做成可運營的系統。讀者先抓 capacity planning、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> tiers 與 self-service metrics 的關係，再看它們怎麼把 operability 變成團隊責任。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 replication latency 上升時，先看 headroom 是否足夠，再看壓測與自動化是否真的覆蓋了常見瓶頸。當 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 需要多層升級時，重點是每一層是否知道何時接手、何時回退，階層形式本身是次要的。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否把容量預測連到實際 growth rate&lt;/li>
&lt;li>能否讓 load testing 自動化到可重用&lt;/li>
&lt;li>能否把 metrics collection 做成 self-service&lt;/li>
&lt;li>能否清楚劃分 primary、secondary 與 SME escalation&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>LinkedIn 的焦點是把 operability 變成日常流程，這和 Shopify 的峰值準備、Microsoft 的治理模式、Spotify 的平台化做法都很接近。差別在於 LinkedIn 更強調內部工具與 metrics pipeline，適合拿來當「中型平台如何長大」的範本。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>automated load testing 把壓測變成日常流程，而不是臨時活動。&lt;/li>
&lt;li>self-service metrics 讓團隊不用等平台工程師才能看見關鍵訊號。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> tiers 讓升級與接手邏輯有固定路徑。&lt;/li>
&lt;li>capacity planning 讓 replication latency 與 headroom 直接相連。&lt;/li>
&lt;li>site reliability engineering 讓中型平台開始形成自己的可靠性職能。&lt;/li>
&lt;li>internal tooling 讓 operability 變成平台化能力而不是個人技巧。&lt;/li>
&lt;li>project waterbear 類演練讓內部故障情境能被規律化測試。&lt;/li>
&lt;li>primary / secondary / SME escalation 讓責任與知識分工更清楚。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1&lt;/a>&lt;/td>
 &lt;td>Capacity 與 On-call 分層&lt;/td>
 &lt;td>把容量邊界與值班交接綁成同一套治理節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">L2&lt;/a>&lt;/td>
 &lt;td>Automated Load Testing 與 Forecasting&lt;/td>
 &lt;td>用持續壓測驅動容量預測，取代一次性壓測的容量規劃&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.linkedin.com/20/welcome-linkedin-engineering-blog">Welcome to the LinkedIn Engineering Blog&lt;/a>：LinkedIn Engineering Blog 的入口。&lt;/li>
&lt;li>&lt;a href="https://engineering.linkedin.com/performance/taming-database-replication-latency-capacity-planning">Taming Database Replication Latency by Capacity Planning&lt;/a>：容量規劃與 replication latency 的經典案例。&lt;/li>
&lt;li>&lt;a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2019/eliminating-toil-with-fully-automated-load-testing">Eliminating toil with fully automated load testing&lt;/a>：自動化壓測與 operability 的實踐。&lt;/li>
&lt;li>&lt;a href="https://engineering.linkedin.com/metrics/scaling-collection-self-service-metrics">Scaling the collection of self-service metrics&lt;/a>：metrics pipeline 與可運維性基礎。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>LinkedIn 是大規模社交平台、capacity planning 與 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 結構的工程文章公開度高、是「中型公司如何規模化 SRE」的教學標竿。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Capacity Planning：跨 region / 跨服務的容量預測方法</li>
<li>On-call 結構：primary / secondary / SME escalation</li>
<li>Operability culture：把可運維性納入服務設計門檻</li>
<li>Internal tooling：LinkedIn engineering blog 公開的內部工具設計</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Capacity Planning</td>
          <td>預測模型、headroom、growth rate</td>
      </tr>
      <tr>
          <td>On-call Tiers</td>
          <td>多層 escalation 設計</td>
      </tr>
      <tr>
          <td>Site Reliability Eng</td>
          <td>LinkedIn SRE 組織演化</td>
      </tr>
      <tr>
          <td>Internal Chaos / Drills</td>
          <td>Project Waterbear 等內部演練</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>LinkedIn 這個案例在講的是中大型平台如何把容量規劃、自動化壓測與 metrics 收集做成可運營的系統。讀者先抓 capacity planning、<a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> tiers 與 self-service metrics 的關係，再看它們怎麼把 operability 變成團隊責任。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 replication latency 上升時，先看 headroom 是否足夠，再看壓測與自動化是否真的覆蓋了常見瓶頸。當 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 需要多層升級時，重點是每一層是否知道何時接手、何時回退，階層形式本身是次要的。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否把容量預測連到實際 growth rate</li>
<li>能否讓 load testing 自動化到可重用</li>
<li>能否把 metrics collection 做成 self-service</li>
<li>能否清楚劃分 primary、secondary 與 SME escalation</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>LinkedIn 的焦點是把 operability 變成日常流程，這和 Shopify 的峰值準備、Microsoft 的治理模式、Spotify 的平台化做法都很接近。差別在於 LinkedIn 更強調內部工具與 metrics pipeline，適合拿來當「中型平台如何長大」的範本。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>automated load testing 把壓測變成日常流程，而不是臨時活動。</li>
<li>self-service metrics 讓團隊不用等平台工程師才能看見關鍵訊號。</li>
<li><a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> tiers 讓升級與接手邏輯有固定路徑。</li>
<li>capacity planning 讓 replication latency 與 headroom 直接相連。</li>
<li>site reliability engineering 讓中型平台開始形成自己的可靠性職能。</li>
<li>internal tooling 讓 operability 變成平台化能力而不是個人技巧。</li>
<li>project waterbear 類演練讓內部故障情境能被規律化測試。</li>
<li>primary / secondary / SME escalation 讓責任與知識分工更清楚。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1</a></td>
          <td>Capacity 與 On-call 分層</td>
          <td>把容量邊界與值班交接綁成同一套治理節奏</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">L2</a></td>
          <td>Automated Load Testing 與 Forecasting</td>
          <td>用持續壓測驅動容量預測，取代一次性壓測的容量規劃</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/20/welcome-linkedin-engineering-blog">Welcome to the LinkedIn Engineering Blog</a>：LinkedIn Engineering Blog 的入口。</li>
<li><a href="https://engineering.linkedin.com/performance/taming-database-replication-latency-capacity-planning">Taming Database Replication Latency by Capacity Planning</a>：容量規劃與 replication latency 的經典案例。</li>
<li><a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2019/eliminating-toil-with-fully-automated-load-testing">Eliminating toil with fully automated load testing</a>：自動化壓測與 operability 的實踐。</li>
<li><a href="https://engineering.linkedin.com/metrics/scaling-collection-self-service-metrics">Scaling the collection of self-service metrics</a>：metrics pipeline 與可運維性基礎。</li>
</ul>
]]></content:encoded></item><item><title>Slack</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/</guid><description>&lt;p>Slack 是即時通訊服務、事故時通訊管道本身受影響、是「monitor your own monitor」議題的代表。Slack engineering blog 公開度高、status page 設計細緻。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>通訊管道自身故障：客戶用 Slack 通報 Slack 事故的 paradox&lt;/li>
&lt;li>外部狀態頁設計：細粒度 region / feature 揭露&lt;/li>
&lt;li>WebSocket 連線風暴：reconnection storm 在大規模長連線服務的特殊風險&lt;/li>
&lt;li>跨 workspace 隔離：multi-tenant 事故的部分擴散模式&lt;/li>
&lt;/ul>
&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>2022&lt;/td>
 &lt;td>Jan 全球登入失效&lt;/td>
 &lt;td>配置變更、跨服務依賴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2022&lt;/td>
 &lt;td>2-22 事故&lt;/td>
 &lt;td>reconnection storm、status 揭露&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Slack 這個案例在講的是通訊平台本身失效時，事故通訊也會一起受影響。讀者先抓 Slack status API、service delivery index 與 incident blog 的責任，再把這類事件看成「監控自己的監控」問題。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當登入或連線異常出現時，使用者需要的是清楚知道狀態頁、回復進度與替代通訊方式，術語在此幫助有限。當 reconnection storm 發生時，恢復節奏也要先保住連線，再回頭處理狀態同步。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否讓 status page 與實際事故節奏同步&lt;/li>
&lt;li>能否把通訊工具失效當成獨立風險&lt;/li>
&lt;li>能否清楚說出哪些 workspace 受影響&lt;/li>
&lt;li>能否在恢復時先控制 reconnection 壓力&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Slack 和 Discord、Microsoft 365 一起看，最能理解通訊工具本身失效時的 IR 難點。它也和 Datadog 有關，因為當你連通訊都不能穩定時，監控與狀態揭露就必須先變成對外的第一路由。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2-22 事故顯示通訊平台本身失效時，status 與 incident blog 也會成為核心資產。&lt;/li>
&lt;li>Slack Status API 則是讓客戶能獨立查詢事故與歷史狀態的樣本。&lt;/li>
&lt;li>reconnection storm 讓通訊平台的容量問題直接變成客戶體感。&lt;/li>
&lt;li>service delivery index 反映的是可靠性與對外揭露如何一起運作。&lt;/li>
&lt;li>workspace 層的部分失效讓多租戶通訊平台必須做細粒度揭露。&lt;/li>
&lt;li>monitor your own monitor 是 Slack 這類平台最直接的 IR 警示。&lt;/li>
&lt;li>incident blog 讓對外敘事與對內修復節奏保持一致。&lt;/li>
&lt;li>multi-workspace failure 會把對外通訊也一起拖進事故。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/" data-link-title="Slack：2022 連線恢復與狀態通訊節奏" data-link-desc="在通訊平台自身失效時，如何同步恢復節奏與對外狀態揭露。">SL1&lt;/a>&lt;/td>
 &lt;td>連線恢復與狀態通訊&lt;/td>
 &lt;td>將恢復節奏與外部更新維持同頻&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://api.slack.com/apis/slack-status">Checking up on Slack with the Slack Status API&lt;/a>：Slack 狀態與歷史 incident 的官方 API。&lt;/li>
&lt;li>&lt;a href="https://slack.engineering/slacks-incident-on-2-22-22/">Slack’s Incident on 2-22-22&lt;/a>：Slack 事故技術復盤。&lt;/li>
&lt;li>&lt;a href="https://slack.engineering/a-terrible-horrible-no-good-very-bad-day-at-slack/">A Terrible, Horrible, No-Good, Very Bad Day at Slack&lt;/a>：另一篇詳細事故回顧。&lt;/li>
&lt;li>&lt;a href="https://slack.engineering/service-delivery-index-a-driver-for-reliability/">Service Delivery Index: A Driver for Reliability&lt;/a>：Slack 的可靠性指標與 status 文化。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Slack 是即時通訊服務、事故時通訊管道本身受影響、是「monitor your own monitor」議題的代表。Slack engineering blog 公開度高、status page 設計細緻。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>通訊管道自身故障：客戶用 Slack 通報 Slack 事故的 paradox</li>
<li>外部狀態頁設計：細粒度 region / feature 揭露</li>
<li>WebSocket 連線風暴：reconnection storm 在大規模長連線服務的特殊風險</li>
<li>跨 workspace 隔離：multi-tenant 事故的部分擴散模式</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2022</td>
          <td>Jan 全球登入失效</td>
          <td>配置變更、跨服務依賴</td>
      </tr>
      <tr>
          <td>2022</td>
          <td>2-22 事故</td>
          <td>reconnection storm、status 揭露</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Slack 這個案例在講的是通訊平台本身失效時，事故通訊也會一起受影響。讀者先抓 Slack status API、service delivery index 與 incident blog 的責任，再把這類事件看成「監控自己的監控」問題。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當登入或連線異常出現時，使用者需要的是清楚知道狀態頁、回復進度與替代通訊方式，術語在此幫助有限。當 reconnection storm 發生時，恢復節奏也要先保住連線，再回頭處理狀態同步。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否讓 status page 與實際事故節奏同步</li>
<li>能否把通訊工具失效當成獨立風險</li>
<li>能否清楚說出哪些 workspace 受影響</li>
<li>能否在恢復時先控制 reconnection 壓力</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Slack 和 Discord、Microsoft 365 一起看，最能理解通訊工具本身失效時的 IR 難點。它也和 Datadog 有關，因為當你連通訊都不能穩定時，監控與狀態揭露就必須先變成對外的第一路由。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2-22 事故顯示通訊平台本身失效時，status 與 incident blog 也會成為核心資產。</li>
<li>Slack Status API 則是讓客戶能獨立查詢事故與歷史狀態的樣本。</li>
<li>reconnection storm 讓通訊平台的容量問題直接變成客戶體感。</li>
<li>service delivery index 反映的是可靠性與對外揭露如何一起運作。</li>
<li>workspace 層的部分失效讓多租戶通訊平台必須做細粒度揭露。</li>
<li>monitor your own monitor 是 Slack 這類平台最直接的 IR 警示。</li>
<li>incident blog 讓對外敘事與對內修復節奏保持一致。</li>
<li>multi-workspace failure 會把對外通訊也一起拖進事故。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/" data-link-title="Slack：2022 連線恢復與狀態通訊節奏" data-link-desc="在通訊平台自身失效時，如何同步恢復節奏與對外狀態揭露。">SL1</a></td>
          <td>連線恢復與狀態通訊</td>
          <td>將恢復節奏與外部更新維持同頻</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://api.slack.com/apis/slack-status">Checking up on Slack with the Slack Status API</a>：Slack 狀態與歷史 incident 的官方 API。</li>
<li><a href="https://slack.engineering/slacks-incident-on-2-22-22/">Slack’s Incident on 2-22-22</a>：Slack 事故技術復盤。</li>
<li><a href="https://slack.engineering/a-terrible-horrible-no-good-very-bad-day-at-slack/">A Terrible, Horrible, No-Good, Very Bad Day at Slack</a>：另一篇詳細事故回顧。</li>
<li><a href="https://slack.engineering/service-delivery-index-a-driver-for-reliability/">Service Delivery Index: A Driver for Reliability</a>：Slack 的可靠性指標與 status 文化。</li>
</ul>
]]></content:encoded></item><item><title>8.11 Go 公開原始碼讀碼路線</title><link>https://tarrragon.github.io/blog/go/08-case-studies/open-source-code-reading/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/open-source-code-reading/</guid><description>&lt;p>Go 公開原始碼讀碼的核心策略是先找服務形狀，再追細節。成熟 Go 專案通常程式量很大；直接從底層型別開始讀，容易失去方向。比較穩定的路線是從入口、組裝、邊界、並發 owner、測試逐步往內走。&lt;/p>
&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>process 如何啟動、config 如何載入&lt;/td>
 &lt;td>&lt;code>cmd/.../main.go&lt;/code>、&lt;code>main.go&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>組裝&lt;/td>
 &lt;td>具體依賴在哪裡建立&lt;/td>
 &lt;td>&lt;code>New...&lt;/code>、&lt;code>Run&lt;/code>、&lt;code>Server&lt;/code>、&lt;code>App&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>邊界&lt;/td>
 &lt;td>HTTP、CLI、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a>、storage 如何接進 application&lt;/td>
 &lt;td>&lt;code>handler&lt;/code>、&lt;code>client&lt;/code>、&lt;code>store&lt;/code>、&lt;code>adapter&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發 owner&lt;/td>
 &lt;td>哪個元件擁有 goroutine、channel、context&lt;/td>
 &lt;td>&lt;code>controller&lt;/code>、&lt;code>worker&lt;/code>、&lt;code>manager&lt;/code>、&lt;code>hub&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試&lt;/td>
 &lt;td>行為如何被固定成案例&lt;/td>
 &lt;td>&lt;code>*_test.go&lt;/code>、test fake、integration test&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="入口先看-process-如何開始">入口：先看 process 如何開始&lt;/h3>
&lt;p>入口檔案的核心價值是揭露服務的第一層責任。讀 &lt;code>main.go&lt;/code> 或 &lt;code>cmd/.../main.go&lt;/code> 時，先找 config、logger、server、worker、signal handling 與 shutdown。這些線索能幫你理解專案是 CLI、daemon、API service、controller，或混合型工具。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/01-basics/main-flow/" data-link-title="1.7 從入口程式看應用啟動流程" data-link-desc="用入口程式建立 Go 程式的啟動與資料流模型">從入口程式看應用啟動流程&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/composition-root/" data-link-title="7.7 composition root 與依賴組裝" data-link-desc="把具體 adapter、config 與 usecase wiring 留在應用入口層">composition root 與依賴組裝&lt;/a>。&lt;/p>
&lt;h3 id="組裝再看具體依賴在哪裡建立">組裝：再看具體依賴在哪裡建立&lt;/h3>
&lt;p>組裝層的核心問題是「誰依賴誰」。成熟專案常有多個 constructor、option struct 或 wiring function。讀碼時可以先畫出 logger、config、storage、client、queue、handler、worker 之間的方向，再進入單一元件細節。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/02-types-data/interfaces/" data-link-title="2.3 interface：用行為定義依賴" data-link-desc="用小介面描述元件需要的能力">interface：用行為定義依賴&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/interface-boundary/" data-link-title="7.2 用 interface 隔離外部依賴" data-link-desc="建立小而穩定的測試替身">用 interface 隔離外部依賴&lt;/a>。&lt;/p>
&lt;h3 id="邊界接著辨識外部世界如何進入程式">邊界：接著辨識外部世界如何進入程式&lt;/h3>
&lt;p>邊界層的核心責任是把外部格式轉成 application 能理解的 command 或資料。HTTP body、CLI flag、queue message、SQL row、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> frame 都屬於邊界格式。讀碼時可以先確認轉換發生在哪裡，避免把 transport、domain 與 storage model 混在一起解讀。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/glossary/" data-link-title="Go 教材核心術語" data-link-desc="整理 Go 入門與進階篇共用的架構、事件、狀態與邊界詞彙">Go 教材核心術語&lt;/a>。&lt;/p>
&lt;h3 id="並發-owner最後追-goroutine-與-channel-的生命週期">並發 owner：最後追 goroutine 與 channel 的生命週期&lt;/h3>
&lt;p>並發 owner 的核心責任是決定 goroutine 何時啟動、如何接收工作、何時停止、錯誤如何回報。看到 &lt;code>go ...&lt;/code>、&lt;code>select&lt;/code>、&lt;code>context.Context&lt;/code>、&lt;code>WaitGroup&lt;/code>、&lt;code>close(ch)&lt;/code> 時，先找 owner。owner 找到後，再判斷資料是否需要 mutex、copy boundary 或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a>。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/01-concurrency-patterns/channel-ownership/" data-link-title="1.1 channel ownership 與關閉責任" data-link-desc="判斷誰能送出、接收與關閉 channel">channel ownership 與關閉責任&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/01-concurrency-patterns/shared-state/" data-link-title="1.4 共享狀態與複製邊界" data-link-desc="用 lock 與 copy 保護長期服務的狀態資料">共享狀態與複製邊界&lt;/a>。&lt;/p>
&lt;h3 id="測試用測試確認真正合約">測試：用測試確認真正合約&lt;/h3>
&lt;p>測試的核心價值是把專案承認的行為寫成可重現案例。成熟專案的測試常比 README 更接近實際合約。讀測試時先看 table-driven case、fake dependency、race test 與 integration test，再回頭理解 production code 的邊界。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/05-error-testing/table-driven-test/" data-link-title="5.3 table-driven test" data-link-desc="用表格整理多組輸入、預期輸出與錯誤情境">table-driven test&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/05-testing-reliability/" data-link-title="模組五：測試與可靠性" data-link-desc="時間控制、WebSocket integration test、race check 與 table-driven test">測試與可靠性&lt;/a>。&lt;/p>
&lt;h2 id="讀碼檢查">讀碼檢查&lt;/h2>
&lt;p>每讀完一個元件，請確認三件事：這個元件擁有什麼狀態、依賴哪些能力、對外承諾哪些行為。這三件事清楚後，再看細節函式會更有效率。&lt;/p></description><content:encoded><![CDATA[<p>Go 公開原始碼讀碼的核心策略是先找服務形狀，再追細節。成熟 Go 專案通常程式量很大；直接從底層型別開始讀，容易失去方向。比較穩定的路線是從入口、組裝、邊界、並發 owner、測試逐步往內走。</p>
<h2 id="讀碼路線">讀碼路線</h2>
<table>
  <thead>
      <tr>
          <th>步驟</th>
          <th>觀察目標</th>
          <th>常見位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>入口</td>
          <td>process 如何啟動、config 如何載入</td>
          <td><code>cmd/.../main.go</code>、<code>main.go</code></td>
      </tr>
      <tr>
          <td>組裝</td>
          <td>具體依賴在哪裡建立</td>
          <td><code>New...</code>、<code>Run</code>、<code>Server</code>、<code>App</code></td>
      </tr>
      <tr>
          <td>邊界</td>
          <td>HTTP、CLI、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a>、storage 如何接進 application</td>
          <td><code>handler</code>、<code>client</code>、<code>store</code>、<code>adapter</code></td>
      </tr>
      <tr>
          <td>並發 owner</td>
          <td>哪個元件擁有 goroutine、channel、context</td>
          <td><code>controller</code>、<code>worker</code>、<code>manager</code>、<code>hub</code></td>
      </tr>
      <tr>
          <td>測試</td>
          <td>行為如何被固定成案例</td>
          <td><code>*_test.go</code>、test fake、integration test</td>
      </tr>
  </tbody>
</table>
<h3 id="入口先看-process-如何開始">入口：先看 process 如何開始</h3>
<p>入口檔案的核心價值是揭露服務的第一層責任。讀 <code>main.go</code> 或 <code>cmd/.../main.go</code> 時，先找 config、logger、server、worker、signal handling 與 shutdown。這些線索能幫你理解專案是 CLI、daemon、API service、controller，或混合型工具。</p>
<p>對應章節：<a href="/blog/go/01-basics/main-flow/" data-link-title="1.7 從入口程式看應用啟動流程" data-link-desc="用入口程式建立 Go 程式的啟動與資料流模型">從入口程式看應用啟動流程</a>、<a href="/blog/go/07-refactoring/composition-root/" data-link-title="7.7 composition root 與依賴組裝" data-link-desc="把具體 adapter、config 與 usecase wiring 留在應用入口層">composition root 與依賴組裝</a>。</p>
<h3 id="組裝再看具體依賴在哪裡建立">組裝：再看具體依賴在哪裡建立</h3>
<p>組裝層的核心問題是「誰依賴誰」。成熟專案常有多個 constructor、option struct 或 wiring function。讀碼時可以先畫出 logger、config、storage、client、queue、handler、worker 之間的方向，再進入單一元件細節。</p>
<p>對應章節：<a href="/blog/go/02-types-data/interfaces/" data-link-title="2.3 interface：用行為定義依賴" data-link-desc="用小介面描述元件需要的能力">interface：用行為定義依賴</a>、<a href="/blog/go/07-refactoring/interface-boundary/" data-link-title="7.2 用 interface 隔離外部依賴" data-link-desc="建立小而穩定的測試替身">用 interface 隔離外部依賴</a>。</p>
<h3 id="邊界接著辨識外部世界如何進入程式">邊界：接著辨識外部世界如何進入程式</h3>
<p>邊界層的核心責任是把外部格式轉成 application 能理解的 command 或資料。HTTP body、CLI flag、queue message、SQL row、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> frame 都屬於邊界格式。讀碼時可以先確認轉換發生在哪裡，避免把 transport、domain 與 storage model 混在一起解讀。</p>
<p>對應章節：<a href="/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元</a>、<a href="/blog/go/glossary/" data-link-title="Go 教材核心術語" data-link-desc="整理 Go 入門與進階篇共用的架構、事件、狀態與邊界詞彙">Go 教材核心術語</a>。</p>
<h3 id="並發-owner最後追-goroutine-與-channel-的生命週期">並發 owner：最後追 goroutine 與 channel 的生命週期</h3>
<p>並發 owner 的核心責任是決定 goroutine 何時啟動、如何接收工作、何時停止、錯誤如何回報。看到 <code>go ...</code>、<code>select</code>、<code>context.Context</code>、<code>WaitGroup</code>、<code>close(ch)</code> 時，先找 owner。owner 找到後，再判斷資料是否需要 mutex、copy boundary 或 <a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a>。</p>
<p>對應章節：<a href="/blog/go-advanced/01-concurrency-patterns/channel-ownership/" data-link-title="1.1 channel ownership 與關閉責任" data-link-desc="判斷誰能送出、接收與關閉 channel">channel ownership 與關閉責任</a>、<a href="/blog/go-advanced/01-concurrency-patterns/shared-state/" data-link-title="1.4 共享狀態與複製邊界" data-link-desc="用 lock 與 copy 保護長期服務的狀態資料">共享狀態與複製邊界</a>。</p>
<h3 id="測試用測試確認真正合約">測試：用測試確認真正合約</h3>
<p>測試的核心價值是把專案承認的行為寫成可重現案例。成熟專案的測試常比 README 更接近實際合約。讀測試時先看 table-driven case、fake dependency、race test 與 integration test，再回頭理解 production code 的邊界。</p>
<p>對應章節：<a href="/blog/go/05-error-testing/table-driven-test/" data-link-title="5.3 table-driven test" data-link-desc="用表格整理多組輸入、預期輸出與錯誤情境">table-driven test</a>、<a href="/blog/go-advanced/05-testing-reliability/" data-link-title="模組五：測試與可靠性" data-link-desc="時間控制、WebSocket integration test、race check 與 table-driven test">測試與可靠性</a>。</p>
<h2 id="讀碼檢查">讀碼檢查</h2>
<p>每讀完一個元件，請確認三件事：這個元件擁有什麼狀態、依賴哪些能力、對外承諾哪些行為。這三件事清楚後，再看細節函式會更有效率。</p>
]]></content:encoded></item><item><title>4.C12 Cloudflare：內部觀測平台的三層能力</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/</guid><description>&lt;p>Cloudflare 的觀測架構把 monitoring、analytics 和 forensics 拆成三層 pipeline，三層各自承擔不同的 resolution、retention 和查詢模式。規模到達每秒數十億 request、300+ edge location 時，用同一套 pipeline 處理三種能力會同時在成本跟查詢延遲上碰壁。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Cloudflare 的服務涵蓋 CDN、DNS、DDoS 防護、Workers 邊緣運算與 Zero Trust 安全。每秒處理數十億 HTTP request，分布在全球 300+ 資料中心。觀測資料量極大 — 僅 HTTP request log 每秒就產生數百 GB 未壓縮的結構化日誌。&lt;/p>
&lt;p>早期觀測用單一 pipeline 處理所有資料，隨著資料量成長，pipeline 面臨三個壓力：monitoring 需要秒級即時性但不需要全量資料；analytics 需要完整資料但可以延遲分鐘級；forensics（鑑識）需要保留原始事件但查詢頻率極低。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="資料量與成本">資料量與成本&lt;/h3>
&lt;p>每秒數十億 request 的全量日誌，即使壓縮後仍是 PB 級月儲存量。把全量資料送到集中式 log backend（無論是自建 Elasticsearch 或 SaaS Datadog）的 ingestion 成本本身就是天文數字。&lt;/p>
&lt;p>Cloudflare 公開表示過去曾用過 Kafka + Elasticsearch + Grafana 的組合，但隨著 edge 節點增加，centralized ingestion 的頻寬跟儲存成本持續超線性成長。&lt;/p>
&lt;h3 id="edge-到-core-的延遲">Edge 到 Core 的延遲&lt;/h3>
&lt;p>觀測資料從 300+ edge 節點匯聚到中心叢集，網路延遲跟 bandwidth 是物理限制。monitoring 需要秒級判斷（alert 要快觸發），但全量日誌的傳輸延遲可能是分鐘級。&lt;/p>
&lt;h3 id="查詢模式衝突">查詢模式衝突&lt;/h3>
&lt;p>on-call 值班需要的是 dashboard 上的 aggregated metrics（error rate、latency percentile、traffic volume），查詢要快、資料要即時。analytics 團隊需要的是全量日誌做 ad-hoc 查詢（某個 IP 在過去 24 小時的 request pattern），查詢可以慢、但資料要完整。forensics 需要的是單一事件的原始內容（某筆 request 的完整 header 跟 body），查詢極少但需要保留數月。&lt;/p>
&lt;p>三種查詢模式在 resolution、freshness 跟 retention 上的需求完全不同，用同一套 backend 處理會讓所有人的體驗都變差。&lt;/p>
&lt;h2 id="解法三層觀測能力">解法：三層觀測能力&lt;/h2>
&lt;h3 id="monitoringpre-aggregated-metrics--alerting">Monitoring：pre-aggregated metrics + alerting&lt;/h3>
&lt;p>edge 節點在本地做 pre-aggregation — 把每秒的 request count、error count、latency histogram 聚合成每 10 秒的 metric batch，push 到中心的 metrics backend。資料量從 PB/月壓縮到 TB/月。&lt;/p>
&lt;p>Alerting 跟 dashboard 只看聚合後的 metrics，查詢延遲在毫秒級。metrics backend 用 Prometheus-compatible 儲存，Grafana 作為查詢入口。&lt;/p>
&lt;h3 id="analyticssampled--full-fidelity-log-pipeline">Analytics：sampled + full-fidelity log pipeline&lt;/h3>
&lt;p>analytics 層接收全量日誌但做分層處理：高流量 endpoint 的日誌做 adaptive sampling（保留 1%-10%），低流量跟異常 request 保留全量。日誌送到自建的 columnar store（Cloudflare 用 ClickHouse 類的 OLAP 引擎），支援 ad-hoc 查詢。&lt;/p>
&lt;p>Retention 30-90 天，查詢延遲在秒到分鐘級。成本比 monitoring 層高但仍可控 — sampling 是關鍵的成本旋鈕。&lt;/p>
&lt;h3 id="forensics原始事件歸檔">Forensics：原始事件歸檔&lt;/h3>
&lt;p>需要完整保留的事件（安全事件、DDoS 攻擊、客戶投訴關聯的 request）寫入冷儲存（object storage）。查詢走 batch 模式（scan-based），延遲在分鐘到小時級。&lt;/p>
&lt;p>Retention 按合規需求保留 6 個月到數年。成本主要是儲存（object storage 便宜），ingestion 跟 query 成本極低。&lt;/p>
&lt;h2 id="取捨">取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>單一 pipeline&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>差（全量資料走同一條路，成本隨 traffic 線性成長）&lt;/td>
 &lt;td>好（每層各自有成本旋鈕）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查詢一致性&lt;/td>
 &lt;td>高（同一個 backend 查）&lt;/td>
 &lt;td>低（三個 backend，查詢語言可能不同）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Freshness&lt;/td>
 &lt;td>被最慢的一段拖住&lt;/td>
 &lt;td>每層獨立（monitoring 秒級、analytics 分鐘級、forensics 小時級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Debugging 路徑&lt;/td>
 &lt;td>短（一個入口）&lt;/td>
 &lt;td>長（先看 monitoring 判斷層級、再決定進 analytics 或 forensics）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三層拆分的最大風險是 debugging 路徑變長 — on-call 先看 dashboard 發現異常，再到 analytics 查 sampled log 找 pattern，最後到 forensics 查原始事件確認細節。如果三層之間的 correlation ID（trace ID、request ID）沒有對齊，跨層查詢會斷掉。&lt;/p></description><content:encoded><![CDATA[<p>Cloudflare 的觀測架構把 monitoring、analytics 和 forensics 拆成三層 pipeline，三層各自承擔不同的 resolution、retention 和查詢模式。規模到達每秒數十億 request、300+ edge location 時，用同一套 pipeline 處理三種能力會同時在成本跟查詢延遲上碰壁。</p>
<h2 id="業務背景">業務背景</h2>
<p>Cloudflare 的服務涵蓋 CDN、DNS、DDoS 防護、Workers 邊緣運算與 Zero Trust 安全。每秒處理數十億 HTTP request，分布在全球 300+ 資料中心。觀測資料量極大 — 僅 HTTP request log 每秒就產生數百 GB 未壓縮的結構化日誌。</p>
<p>早期觀測用單一 pipeline 處理所有資料，隨著資料量成長，pipeline 面臨三個壓力：monitoring 需要秒級即時性但不需要全量資料；analytics 需要完整資料但可以延遲分鐘級；forensics（鑑識）需要保留原始事件但查詢頻率極低。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="資料量與成本">資料量與成本</h3>
<p>每秒數十億 request 的全量日誌，即使壓縮後仍是 PB 級月儲存量。把全量資料送到集中式 log backend（無論是自建 Elasticsearch 或 SaaS Datadog）的 ingestion 成本本身就是天文數字。</p>
<p>Cloudflare 公開表示過去曾用過 Kafka + Elasticsearch + Grafana 的組合，但隨著 edge 節點增加，centralized ingestion 的頻寬跟儲存成本持續超線性成長。</p>
<h3 id="edge-到-core-的延遲">Edge 到 Core 的延遲</h3>
<p>觀測資料從 300+ edge 節點匯聚到中心叢集，網路延遲跟 bandwidth 是物理限制。monitoring 需要秒級判斷（alert 要快觸發），但全量日誌的傳輸延遲可能是分鐘級。</p>
<h3 id="查詢模式衝突">查詢模式衝突</h3>
<p>on-call 值班需要的是 dashboard 上的 aggregated metrics（error rate、latency percentile、traffic volume），查詢要快、資料要即時。analytics 團隊需要的是全量日誌做 ad-hoc 查詢（某個 IP 在過去 24 小時的 request pattern），查詢可以慢、但資料要完整。forensics 需要的是單一事件的原始內容（某筆 request 的完整 header 跟 body），查詢極少但需要保留數月。</p>
<p>三種查詢模式在 resolution、freshness 跟 retention 上的需求完全不同，用同一套 backend 處理會讓所有人的體驗都變差。</p>
<h2 id="解法三層觀測能力">解法：三層觀測能力</h2>
<h3 id="monitoringpre-aggregated-metrics--alerting">Monitoring：pre-aggregated metrics + alerting</h3>
<p>edge 節點在本地做 pre-aggregation — 把每秒的 request count、error count、latency histogram 聚合成每 10 秒的 metric batch，push 到中心的 metrics backend。資料量從 PB/月壓縮到 TB/月。</p>
<p>Alerting 跟 dashboard 只看聚合後的 metrics，查詢延遲在毫秒級。metrics backend 用 Prometheus-compatible 儲存，Grafana 作為查詢入口。</p>
<h3 id="analyticssampled--full-fidelity-log-pipeline">Analytics：sampled + full-fidelity log pipeline</h3>
<p>analytics 層接收全量日誌但做分層處理：高流量 endpoint 的日誌做 adaptive sampling（保留 1%-10%），低流量跟異常 request 保留全量。日誌送到自建的 columnar store（Cloudflare 用 ClickHouse 類的 OLAP 引擎），支援 ad-hoc 查詢。</p>
<p>Retention 30-90 天，查詢延遲在秒到分鐘級。成本比 monitoring 層高但仍可控 — sampling 是關鍵的成本旋鈕。</p>
<h3 id="forensics原始事件歸檔">Forensics：原始事件歸檔</h3>
<p>需要完整保留的事件（安全事件、DDoS 攻擊、客戶投訴關聯的 request）寫入冷儲存（object storage）。查詢走 batch 模式（scan-based），延遲在分鐘到小時級。</p>
<p>Retention 按合規需求保留 6 個月到數年。成本主要是儲存（object storage 便宜），ingestion 跟 query 成本極低。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>單一 pipeline</th>
          <th>三層拆分</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構複雜度</td>
          <td>低（一條路走完）</td>
          <td>高（三條路各自維護）</td>
      </tr>
      <tr>
          <td>成本可控度</td>
          <td>差（全量資料走同一條路，成本隨 traffic 線性成長）</td>
          <td>好（每層各自有成本旋鈕）</td>
      </tr>
      <tr>
          <td>查詢一致性</td>
          <td>高（同一個 backend 查）</td>
          <td>低（三個 backend，查詢語言可能不同）</td>
      </tr>
      <tr>
          <td>Freshness</td>
          <td>被最慢的一段拖住</td>
          <td>每層獨立（monitoring 秒級、analytics 分鐘級、forensics 小時級）</td>
      </tr>
      <tr>
          <td>Debugging 路徑</td>
          <td>短（一個入口）</td>
          <td>長（先看 monitoring 判斷層級、再決定進 analytics 或 forensics）</td>
      </tr>
  </tbody>
</table>
<p>三層拆分的最大風險是 debugging 路徑變長 — on-call 先看 dashboard 發現異常，再到 analytics 查 sampled log 找 pattern，最後到 forensics 查原始事件確認細節。如果三層之間的 correlation ID（trace ID、request ID）沒有對齊，跨層查詢會斷掉。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 Log Schema</a>：三層共用的欄位設計（correlation ID、timestamp、service tag）是 log schema 的規模化實例。</li>
<li><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3 Tracing Context</a>：跨層 correlation 依賴 trace context propagation，edge → core 的 context 傳遞是挑戰。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：三層拆分就是 pipeline 的 routing 跟 processing 層設計。</li>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a>：三層各自的成本旋鈕（sampling rate、retention、storage tier）是成本歸因的實作入口。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測平台帳單主要被全量日誌 ingestion 佔據，但 90% 的日誌沒人查過</li>
<li>Dashboard 查詢越來越慢，因為查詢打的是存了全量資料的同一個 backend</li>
<li>on-call 跟 analytics 團隊對觀測 backend 的需求衝突（一個要快、一個要全）</li>
<li>edge / CDN / 多 region 架構下，central pipeline 的 ingestion bandwidth 成為瓶頸</li>
<li>安全團隊要求保留原始事件 6 個月以上，但 hot tier 儲存成本撐不住</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/vision-for-observability/">Our Vision for Observability at Cloudflare</a></li>
<li><a href="https://blog.cloudflare.com/building-cloudflare-on-cloudflare/">Building Cloudflare on Cloudflare</a></li>
</ul>
]]></content:encoded></item><item><title>3.C12 Pinterest：Shallow Mirror 優化 MirrorMaker</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/</guid><description>&lt;p>這個案例的核心責任是說明 cross-region replication 的 CPU/memory 成本是被低估的工程議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pinterest 三個 AWS region（us-east-1 / us-east-2 / eu-west-1）跑 MirrorMaker v1、原版設計把 record 解壓+重壓、memory 用量 2-10x 於網路 bytes、CPU spike 與 OOM 頻繁。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Shallow Mirror 在 RecordBatch 層淺迭代 + ByteBuffer pointer 共享、避開 deserialize/re-compress。揭露「跨區同步不是純 I/O 問題、是 CPU + memory + 網路三維壓力」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / MirrorMaker 2 配置。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/shallow-mirror-f543b14bb25">Pinterest Shallow Mirror&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 cross-region replication 的 CPU/memory 成本是被低估的工程議題。</p>
<h2 id="觀察">觀察</h2>
<p>Pinterest 三個 AWS region（us-east-1 / us-east-2 / eu-west-1）跑 MirrorMaker v1、原版設計把 record 解壓+重壓、memory 用量 2-10x 於網路 bytes、CPU spike 與 OOM 頻繁。</p>
<h2 id="判讀">判讀</h2>
<p>Shallow Mirror 在 RecordBatch 層淺迭代 + ByteBuffer pointer 共享、避開 deserialize/re-compress。揭露「跨區同步不是純 I/O 問題、是 CPU + memory + 網路三維壓力」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / MirrorMaker 2 配置。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/shallow-mirror-f543b14bb25">Pinterest Shallow Mirror</a></li>
</ul>
]]></content:encoded></item><item><title>9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/</guid><description>&lt;p>這個案例的核心責任是說明「K8s 多 cluster 治理」對容量規劃的影響。Riot Games 經營 League of Legends、VALORANT、TFT 等多款全球遊戲、單一遊戲跨多地區、需要 &amp;lt; 35ms 延遲、需要做到「快速部署新遊戲 / 新區域」— 這套需求把容量規劃的單位從「instance」改成「cluster」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Riot Games 遷移到 EKS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games case study&lt;/a>）：&lt;/p>
&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>1.8 億 +&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cluster 數量&lt;/td>
 &lt;td>246 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施年省&lt;/td>
 &lt;td>1000 萬美金&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署速度提升&lt;/td>
 &lt;td>12x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施設定速度&lt;/td>
 &lt;td>+90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲門檻&lt;/td>
 &lt;td>35ms（VALORANT 等競技遊戲）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>標準化覆蓋率&lt;/td>
 &lt;td>80% 基礎設施移到中央管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開發者基礎設施工作下降&lt;/td>
 &lt;td>-40%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件回應時間下降&lt;/td>
 &lt;td>-50%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon EKS（主要）、AWS Local Zones（低延遲就近部署）、AWS Outposts（on-prem edge）、Karpenter（node lifecycle）、Terraform（IaC）。&lt;/p>
&lt;p>關鍵架構決策：從 multi-tenant cluster 模型改成 &lt;em>single-tenant per game&lt;/em> — 每個遊戲一個獨立 cluster、避免跨遊戲互相影響。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Riot Games 案例揭露三個多 cluster K8s 容量治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cluster 隔離是容量規劃的單位&lt;/strong>：246 個 cluster 看似很多、但 &lt;em>每個 cluster 是獨立容量單位&lt;/em>、不互相影響。一個遊戲的擴容不會吃掉另一個遊戲的容量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 multi-tenant vs single-tenant 取捨。&lt;/li>
&lt;li>&lt;strong>延遲門檻反推 region 部署&lt;/strong>：35ms 是競技遊戲（VALORANT、League）的可接受上限、超過會「卡」。從這個門檻反推：玩家所在 region 不能跨洲、需要區域 cluster。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget。Local Zones / Outposts 是這個門檻的工程回應。&lt;/li>
&lt;li>&lt;strong>Karpenter + Terraform = cluster 容量自動化&lt;/strong>：246 個 cluster 手動管理會崩。Karpenter（node 動態 lifecycle）+ Terraform（IaC）讓 cluster 級操作可重複、可審查。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop&lt;/a> 的自動化迴圈。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「年省 1000 萬」是 &lt;em>vs 自管 Mesos&lt;/em>、不是 &lt;em>vs 沒上雲&lt;/em>。EKS 仍有 vendor cost、只是比自管便宜。讀案例時要看 baseline 是什麼。另外、單一 cluster 的容量上限（pod 數、node 數）仍是工程現實、超過時要做 cluster sharding（這正是 Riot 走 246 個 cluster 的部分原因）。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>single-tenant cluster per workload&lt;/strong>：每個高敏感度工作負載（每個遊戲、每個關鍵服務）一個獨立 cluster、避免 noisy neighbor。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a>。&lt;/li>
&lt;li>&lt;strong>延遲門檻反推 region 部署數量&lt;/strong>：先訂 latency budget、再算 &lt;em>玩家分布 × region cluster 數量&lt;/em>。region 增加會線性增加 ops 成本、要在 latency 跟 cost 之間找平衡。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>cluster 級 IaC + 自動化是 multi-cluster 治理前置&lt;/strong>：Terraform / Pulumi / Crossplane + Karpenter / Cluster Autoscaler 是基本工具。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE Fleet management（multi-cluster）、Azure Fleet Manager、自建 Cluster API + ArgoCD 都可以做 multi-cluster 治理。差異是 vendor 整合度跟政策。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「K8s 多 cluster 治理」對容量規劃的影響。Riot Games 經營 League of Legends、VALORANT、TFT 等多款全球遊戲、單一遊戲跨多地區、需要 &lt; 35ms 延遲、需要做到「快速部署新遊戲 / 新區域」— 這套需求把容量規劃的單位從「instance」改成「cluster」。</p>
<h2 id="觀察">觀察</h2>
<p>Riot Games 遷移到 EKS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活用戶</td>
          <td>1.8 億 +</td>
      </tr>
      <tr>
          <td>Cluster 數量</td>
          <td>246 個</td>
      </tr>
      <tr>
          <td>基礎設施年省</td>
          <td>1000 萬美金</td>
      </tr>
      <tr>
          <td>部署速度提升</td>
          <td>12x</td>
      </tr>
      <tr>
          <td>基礎設施設定速度</td>
          <td>+90%</td>
      </tr>
      <tr>
          <td>延遲門檻</td>
          <td>35ms（VALORANT 等競技遊戲）</td>
      </tr>
      <tr>
          <td>標準化覆蓋率</td>
          <td>80% 基礎設施移到中央管理</td>
      </tr>
      <tr>
          <td>開發者基礎設施工作下降</td>
          <td>-40%</td>
      </tr>
      <tr>
          <td>事件回應時間下降</td>
          <td>-50%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon EKS（主要）、AWS Local Zones（低延遲就近部署）、AWS Outposts（on-prem edge）、Karpenter（node lifecycle）、Terraform（IaC）。</p>
<p>關鍵架構決策：從 multi-tenant cluster 模型改成 <em>single-tenant per game</em> — 每個遊戲一個獨立 cluster、避免跨遊戲互相影響。</p>
<h2 id="判讀">判讀</h2>
<p>Riot Games 案例揭露三個多 cluster K8s 容量治理重點。</p>
<ol>
<li><strong>Cluster 隔離是容量規劃的單位</strong>：246 個 cluster 看似很多、但 <em>每個 cluster 是獨立容量單位</em>、不互相影響。一個遊戲的擴容不會吃掉另一個遊戲的容量。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 multi-tenant vs single-tenant 取捨。</li>
<li><strong>延遲門檻反推 region 部署</strong>：35ms 是競技遊戲（VALORANT、League）的可接受上限、超過會「卡」。從這個門檻反推：玩家所在 region 不能跨洲、需要區域 cluster。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget。Local Zones / Outposts 是這個門檻的工程回應。</li>
<li><strong>Karpenter + Terraform = cluster 容量自動化</strong>：246 個 cluster 手動管理會崩。Karpenter（node 動態 lifecycle）+ Terraform（IaC）讓 cluster 級操作可重複、可審查。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> 的自動化迴圈。</li>
</ol>
<p>需要警惕：「年省 1000 萬」是 <em>vs 自管 Mesos</em>、不是 <em>vs 沒上雲</em>。EKS 仍有 vendor cost、只是比自管便宜。讀案例時要看 baseline 是什麼。另外、單一 cluster 的容量上限（pod 數、node 數）仍是工程現實、超過時要做 cluster sharding（這正是 Riot 走 246 個 cluster 的部分原因）。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>single-tenant cluster per workload</strong>：每個高敏感度工作負載（每個遊戲、每個關鍵服務）一個獨立 cluster、避免 noisy neighbor。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
<li><strong>延遲門檻反推 region 部署數量</strong>：先訂 latency budget、再算 <em>玩家分布 × region cluster 數量</em>。region 增加會線性增加 ops 成本、要在 latency 跟 cost 之間找平衡。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>cluster 級 IaC + 自動化是 multi-cluster 治理前置</strong>：Terraform / Pulumi / Crossplane + Karpenter / Cluster Autoscaler 是基本工具。</li>
</ol>
<p>跨平台等效：GCP GKE Fleet management（multi-cluster）、Azure Fleet Manager、自建 Cluster API + ArgoCD 都可以做 multi-cluster 治理。差異是 vendor 整合度跟政策。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 multi-cluster K8s → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做延遲門檻反推部署 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a></li>
<li>想對照微服務 vs multi-cluster → <a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games Cuts $10M Annual Infrastructure Costs by Migrating to Amazon EKS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/riot-games-reinvent/">Riot Games on Using AWS to Improve Gaming</a></li>
</ul>
]]></content:encoded></item><item><title>Google：Postmortem Action Item Closure 治理</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/</guid><description>&lt;p>Postmortem 的核心責任是把事故轉成會被完成的工程改進，解釋事故只是第一步。Google 的做法重點在 action item closure：每個改進項都要有 owner、完成條件、追蹤節奏與逾期處理規則。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>很多團隊 postmortem 寫得完整，但事故仍反覆發生。根因通常是 action item 沒有被制度化追蹤，分析能力本身不是瓶頸。當改進工作和日常 feature 競爭同一批資源時，沒有 closure 機制的 action item 很容易被延後到失效。&lt;/p>
&lt;h2 id="治理機制">治理機制&lt;/h2>
&lt;p>可靠的 closure 機制要先把 action item 分級，再對應不同完成標準。&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>P0&lt;/td>
 &lt;td>重複事故高機率再發生&lt;/td>
 &lt;td>需在下個 release 週期前完成並驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P1&lt;/td>
 &lt;td>會放大事故影響面&lt;/td>
 &lt;td>要有落地日期與 gate 條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>P2&lt;/td>
 &lt;td>提升診斷或操作效率&lt;/td>
 &lt;td>可排入 backlog，但要保留追蹤節點&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分級之後要做三件事：&lt;/p>
&lt;ol>
&lt;li>為每個 action item 指派單一 owner。&lt;/li>
&lt;li>寫出可驗證完成條件（不是「優化」「強化」這類抽象字）。&lt;/li>
&lt;li>把 closure 狀態納入固定 review cadence。&lt;/li>
&lt;/ol>
&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>overdue action-item ratio&lt;/td>
 &lt;td>是否長期積壓高風險改進&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">8.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>repeated-incident similarity&lt;/td>
 &lt;td>同型事故是否仍反覆發生&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/repeated-incident-toil/" data-link-title="8.13 Repeated Incident 與 Toil 治理" data-link-desc="把同型事故反覆發生與重複手動修復作為工程化治理對象">8.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>gate bypass count&lt;/td>
 &lt;td>是否在高風險情況下跳過治理閘門&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>verification evidence coverage&lt;/td>
 &lt;td>完成項是否附驗證證據&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>最常見陷阱是把 action item 當作「會後待辦」而不是 release policy 的一部分。這會讓高風險改進沒有實際約束力。正確做法是把 P0/P1 項目直接綁到 release gate，未完成時不得放行關聯變更。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a> 保留 action item 的決策脈絡，再到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a> 回寫觀測與驗證項目。若要把 closure 變成制度，回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Reliability Debt Backlog&lt;/a> 進行排序治理。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sre.google/sre-book/table-of-contents/">Google SRE Book&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://sre.google/workbook/table-of-contents/">Google SRE Workbook&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Postmortem 的核心責任是把事故轉成會被完成的工程改進，解釋事故只是第一步。Google 的做法重點在 action item closure：每個改進項都要有 owner、完成條件、追蹤節奏與逾期處理規則。</p>
<h2 id="問題場景">問題場景</h2>
<p>很多團隊 postmortem 寫得完整，但事故仍反覆發生。根因通常是 action item 沒有被制度化追蹤，分析能力本身不是瓶頸。當改進工作和日常 feature 競爭同一批資源時，沒有 closure 機制的 action item 很容易被延後到失效。</p>
<h2 id="治理機制">治理機制</h2>
<p>可靠的 closure 機制要先把 action item 分級，再對應不同完成標準。</p>
<table>
  <thead>
      <tr>
          <th>分級</th>
          <th>風險型態</th>
          <th>最低完成標準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>P0</td>
          <td>重複事故高機率再發生</td>
          <td>需在下個 release 週期前完成並驗證</td>
      </tr>
      <tr>
          <td>P1</td>
          <td>會放大事故影響面</td>
          <td>要有落地日期與 gate 條件</td>
      </tr>
      <tr>
          <td>P2</td>
          <td>提升診斷或操作效率</td>
          <td>可排入 backlog，但要保留追蹤節點</td>
      </tr>
  </tbody>
</table>
<p>分級之後要做三件事：</p>
<ol>
<li>為每個 action item 指派單一 owner。</li>
<li>寫出可驗證完成條件（不是「優化」「強化」這類抽象字）。</li>
<li>把 closure 狀態納入固定 review cadence。</li>
</ol>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>overdue action-item ratio</td>
          <td>是否長期積壓高風險改進</td>
          <td><a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">8.5</a></td>
      </tr>
      <tr>
          <td>repeated-incident similarity</td>
          <td>同型事故是否仍反覆發生</td>
          <td><a href="/blog/backend/08-incident-response/repeated-incident-toil/" data-link-title="8.13 Repeated Incident 與 Toil 治理" data-link-desc="把同型事故反覆發生與重複手動修復作為工程化治理對象">8.13</a></td>
      </tr>
      <tr>
          <td>gate bypass count</td>
          <td>是否在高風險情況下跳過治理閘門</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>verification evidence coverage</td>
          <td>完成項是否附驗證證據</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>最常見陷阱是把 action item 當作「會後待辦」而不是 release policy 的一部分。這會讓高風險改進沒有實際約束力。正確做法是把 P0/P1 項目直接綁到 release gate，未完成時不得放行關聯變更。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先在 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a> 保留 action item 的決策脈絡，再到 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a> 回寫觀測與驗證項目。若要把 closure 變成制度，回到 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Reliability Debt Backlog</a> 進行排序治理。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sre.google/sre-book/table-of-contents/">Google SRE Book</a></li>
<li><a href="https://sre.google/workbook/table-of-contents/">Google SRE Workbook</a></li>
</ul>
]]></content:encoded></item><item><title>Datadog</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/</guid><description>&lt;p>Datadog 2023 multi-region 事故是「監控供應商自己掛」的代表案例。當客戶依賴的 observability 平台失效、客戶失去判讀自己服務的能力、IR 流程出現 second-order 影響。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>監控失效的 second-order 影響：客戶失去判讀工具、無法自我評估事故規模&lt;/li>
&lt;li>Multi-region 同時失效：region 隔離假設破裂時的全面失明&lt;/li>
&lt;li>客戶溝通：監控廠商如何向「正在 blind 的客戶」溝通&lt;/li>
&lt;li>自我監控：observability 廠商的 self-observability 設計&lt;/li>
&lt;/ul>
&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>2023-03&lt;/td>
 &lt;td>Multi-region 全球停擺&lt;/td>
 &lt;td>region 隔離破裂、客戶觀測落差&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Datadog 這個案例在講的是監控供應商自己失效時，客戶會同時失去判讀與協作能力。讀者先抓 multi-region、status page 與 incident management 的責任，再把 observability outage 看成 second-order 風險。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當監控平台自己出現連線或區域問題時，最先失去的是判讀服務健康的能力，資料本身通常還在。當客戶仍在 blind 狀態時，對外溝通與備援觀測通道就要先回來，否則事故會因資訊不足而延長。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否辨認 observability 平台本身就是依賴&lt;/li>
&lt;li>能否把 multi-region 隔離失效視為核心風險&lt;/li>
&lt;li>能否提供客戶替代觀測路徑&lt;/li>
&lt;li>能否把 self-observability 放進平台設計&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Datadog 這頁最適合和 Honeycomb、Slack 一起看：前者是觀測平台本身，後者是事故通訊路徑。三者放在一起時，讀者會更清楚地看到「當你看不見系統時，連協作也會失明」這件事怎麼發生。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2023 multi-region 事故說明監控廠商自己也會失明。&lt;/li>
&lt;li>status page 與 incident management 的銜接，決定客戶能否持續觀測自己服務。&lt;/li>
&lt;li>客戶在 blind 狀態時需要備援觀測路徑。&lt;/li>
&lt;li>self-observability 是 observability 廠商自己的基本要求。&lt;/li>
&lt;li>multi-region 同時失效會讓區域隔離假設失靈。&lt;/li>
&lt;li>incident response 的第一優先是把客戶從盲區拉回來。&lt;/li>
&lt;li>observability 平台失效會造成 second-order 事故。&lt;/li>
&lt;li>status page 與 incident workflow 需要維持同一條節奏。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/" data-link-title="Datadog：2023 多區觀測中斷事件" data-link-desc="監控平台自身退化時，如何避免客戶誤判系統健康狀態。">DD1&lt;/a>&lt;/td>
 &lt;td>多區觀測中斷&lt;/td>
 &lt;td>處理監控平台失效造成的判讀盲區&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.datadoghq.com/blog/2023-03-08-multiregion-infrastructure-connectivity-issue/">2023-03-08 Incident: Infrastructure connectivity issue affecting multiple regions&lt;/a>：Datadog 2023 多區事故的官方回顧。&lt;/li>
&lt;li>&lt;a href="https://www.datadoghq.com/blog/how-datadog-manages-incidents/">How we manage incidents at Datadog&lt;/a>：Datadog incident response 與 postmortem 的流程。&lt;/li>
&lt;li>&lt;a href="https://docs.datadoghq.com/incident_response/status_pages/">Status Pages&lt;/a>：Datadog status page 的官方文件。&lt;/li>
&lt;li>&lt;a href="https://docs.datadoghq.com/incident_response/incident_management/integrations/statuspage/">Integrate Atlassian Statuspage with Datadog Incident Management&lt;/a>：Statuspage 與 incident management 的交接。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Datadog 2023 multi-region 事故是「監控供應商自己掛」的代表案例。當客戶依賴的 observability 平台失效、客戶失去判讀自己服務的能力、IR 流程出現 second-order 影響。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>監控失效的 second-order 影響：客戶失去判讀工具、無法自我評估事故規模</li>
<li>Multi-region 同時失效：region 隔離假設破裂時的全面失明</li>
<li>客戶溝通：監控廠商如何向「正在 blind 的客戶」溝通</li>
<li>自我監控：observability 廠商的 self-observability 設計</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2023-03</td>
          <td>Multi-region 全球停擺</td>
          <td>region 隔離破裂、客戶觀測落差</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Datadog 這個案例在講的是監控供應商自己失效時，客戶會同時失去判讀與協作能力。讀者先抓 multi-region、status page 與 incident management 的責任，再把 observability outage 看成 second-order 風險。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當監控平台自己出現連線或區域問題時，最先失去的是判讀服務健康的能力，資料本身通常還在。當客戶仍在 blind 狀態時，對外溝通與備援觀測通道就要先回來，否則事故會因資訊不足而延長。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否辨認 observability 平台本身就是依賴</li>
<li>能否把 multi-region 隔離失效視為核心風險</li>
<li>能否提供客戶替代觀測路徑</li>
<li>能否把 self-observability 放進平台設計</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Datadog 這頁最適合和 Honeycomb、Slack 一起看：前者是觀測平台本身，後者是事故通訊路徑。三者放在一起時，讀者會更清楚地看到「當你看不見系統時，連協作也會失明」這件事怎麼發生。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2023 multi-region 事故說明監控廠商自己也會失明。</li>
<li>status page 與 incident management 的銜接，決定客戶能否持續觀測自己服務。</li>
<li>客戶在 blind 狀態時需要備援觀測路徑。</li>
<li>self-observability 是 observability 廠商自己的基本要求。</li>
<li>multi-region 同時失效會讓區域隔離假設失靈。</li>
<li>incident response 的第一優先是把客戶從盲區拉回來。</li>
<li>observability 平台失效會造成 second-order 事故。</li>
<li>status page 與 incident workflow 需要維持同一條節奏。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/" data-link-title="Datadog：2023 多區觀測中斷事件" data-link-desc="監控平台自身退化時，如何避免客戶誤判系統健康狀態。">DD1</a></td>
          <td>多區觀測中斷</td>
          <td>處理監控平台失效造成的判讀盲區</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.datadoghq.com/blog/2023-03-08-multiregion-infrastructure-connectivity-issue/">2023-03-08 Incident: Infrastructure connectivity issue affecting multiple regions</a>：Datadog 2023 多區事故的官方回顧。</li>
<li><a href="https://www.datadoghq.com/blog/how-datadog-manages-incidents/">How we manage incidents at Datadog</a>：Datadog incident response 與 postmortem 的流程。</li>
<li><a href="https://docs.datadoghq.com/incident_response/status_pages/">Status Pages</a>：Datadog status page 的官方文件。</li>
<li><a href="https://docs.datadoghq.com/incident_response/incident_management/integrations/statuspage/">Integrate Atlassian Statuspage with Datadog Incident Management</a>：Statuspage 與 incident management 的交接。</li>
</ul>
]]></content:encoded></item><item><title>Honeycomb</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/</guid><description>&lt;p>Honeycomb 是 observability platform、由創辦人之一 Charity Majors 推動的 observability-driven SRE 是領域 thought leadership 來源。教學重點在「以 observability 為主軸的 SRE 工程文化」。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>High-cardinality observability：相對 metrics-first 的觀測哲學&lt;/li>
&lt;li>Service Level Objective 實作：SLO budget、burn rate alert&lt;/li>
&lt;li>Test in production：feature flag + observability 的 production testing&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 文化：Charity Majors 的 SRE / on-call 觀點&lt;/li>
&lt;/ul>
&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>Observability Engineering&lt;/td>
 &lt;td>high-cardinality 與 unknown-unknowns&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SLO Burn Rate Alert&lt;/td>
 &lt;td>error budget 速率告警設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Test in Production&lt;/td>
 &lt;td>feature flag + observability 的安全推進&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production Excellence&lt;/td>
 &lt;td>Honeycomb 推動的 SRE 文化框架&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Honeycomb 這個案例在講的是 observability 如何變成工程決策，而不是只剩看板與指標。讀者先抓 high-cardinality、burn rate 與 test in production 這三個原語，再把它們看成觀測能力如何支撐 SRE 文化。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當訊號維度開始膨脹時，重點是先判斷資料還能不能回答問題，增加更多圖表解決不了維度膨脹。當 SLO 進入 burn 速率區間時，觀測系統要能直接幫團隊看見風險，而不是等事故發生後才補證據。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否辨認 high cardinality 何時讓查詢與告警失真&lt;/li>
&lt;li>能否把 SLO burn rate 轉成當下可行動的訊號&lt;/li>
&lt;li>能否在 production testing 中保住 blast radius&lt;/li>
&lt;li>能否把 observability 當成工程責任，而不是 ops 專屬工作&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Honeycomb 把觀測責任直接拉到每個工程團隊，這和 Google 的 SLO 制度、Datadog 的自我觀測、Slack 的狀態揭露形成一組互補視角。當讀者先懂這頁，就比較容易看懂為什麼高 cardinality 與 burn rate 是決策前提，當成報表細節會低估它們的影響。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>high cardinality 讓問題能按 tenant、feature、path 切開，而不是只看總平均。&lt;/li>
&lt;li>burn rate alert 直接把 SLO 消耗速度變成行動訊號。&lt;/li>
&lt;li>test in production 讓觀測訊號在真實流量下被驗證。&lt;/li>
&lt;li>observability engineering 把看板轉成工程決策入口。&lt;/li>
&lt;li>unknown-unknowns 讓觀測系統要先能回答「不知道要查什麼」的問題。&lt;/li>
&lt;li>production excellence 讓 observability 成為每個工程師的日常責任。&lt;/li>
&lt;li>query latency 會反過來告訴你資料建模是否已經失真。&lt;/li>
&lt;li>feature flag 配合觀測訊號，讓 production testing 可以安全推進。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">HC1&lt;/a>&lt;/td>
 &lt;td>Burn Rate 驅動可靠性&lt;/td>
 &lt;td>把 SLO 消耗速度轉成值班與改善優先序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/" data-link-title="Honeycomb：Production Excellence 與 Test in Production" data-link-desc="用 high-cardinality observability 把 production 變成安全的驗證環境：feature flag、progressive rollout 與即時回饋的配合。">HC2&lt;/a>&lt;/td>
 &lt;td>Production Excellence 與 Test in Prod&lt;/td>
 &lt;td>用 observability 把 production 變成安全的驗證環境&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.honeycomb.io/resources/getting-started/what-is-observability-engineering">What Is Observability Engineering?&lt;/a>：Honeycomb 對 observability engineering 的核心定義。&lt;/li>
&lt;li>&lt;a href="https://docs.honeycomb.io/get-started/basics/observability/concepts/high-cardinality">High Cardinality&lt;/a>：高 cardinality / dimensionality 的官方說明。&lt;/li>
&lt;li>&lt;a href="https://docs.honeycomb.io/reference/honeycomb-ui/slos/slo-detail-view/">SLO Detail View&lt;/a>：burn rate 與 budget burndown 的產品視角。&lt;/li>
&lt;li>&lt;a href="https://www.honeycomb.io/blog/observability-every-engineers-job-not-just-ops-problem">Observability: It&amp;rsquo;s Every Engineer’s Job, Not Just Ops’ Problem&lt;/a>：觀測責任不只在 ops 的實踐論述。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Honeycomb 是 observability platform、由創辦人之一 Charity Majors 推動的 observability-driven SRE 是領域 thought leadership 來源。教學重點在「以 observability 為主軸的 SRE 工程文化」。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>High-cardinality observability：相對 metrics-first 的觀測哲學</li>
<li>Service Level Objective 實作：SLO budget、burn rate alert</li>
<li>Test in production：feature flag + observability 的 production testing</li>
<li><a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 文化：Charity Majors 的 SRE / on-call 觀點</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Observability Engineering</td>
          <td>high-cardinality 與 unknown-unknowns</td>
      </tr>
      <tr>
          <td>SLO Burn Rate Alert</td>
          <td>error budget 速率告警設計</td>
      </tr>
      <tr>
          <td>Test in Production</td>
          <td>feature flag + observability 的安全推進</td>
      </tr>
      <tr>
          <td>Production Excellence</td>
          <td>Honeycomb 推動的 SRE 文化框架</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Honeycomb 這個案例在講的是 observability 如何變成工程決策，而不是只剩看板與指標。讀者先抓 high-cardinality、burn rate 與 test in production 這三個原語，再把它們看成觀測能力如何支撐 SRE 文化。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當訊號維度開始膨脹時，重點是先判斷資料還能不能回答問題，增加更多圖表解決不了維度膨脹。當 SLO 進入 burn 速率區間時，觀測系統要能直接幫團隊看見風險，而不是等事故發生後才補證據。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否辨認 high cardinality 何時讓查詢與告警失真</li>
<li>能否把 SLO burn rate 轉成當下可行動的訊號</li>
<li>能否在 production testing 中保住 blast radius</li>
<li>能否把 observability 當成工程責任，而不是 ops 專屬工作</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Honeycomb 把觀測責任直接拉到每個工程團隊，這和 Google 的 SLO 制度、Datadog 的自我觀測、Slack 的狀態揭露形成一組互補視角。當讀者先懂這頁，就比較容易看懂為什麼高 cardinality 與 burn rate 是決策前提，當成報表細節會低估它們的影響。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>high cardinality 讓問題能按 tenant、feature、path 切開，而不是只看總平均。</li>
<li>burn rate alert 直接把 SLO 消耗速度變成行動訊號。</li>
<li>test in production 讓觀測訊號在真實流量下被驗證。</li>
<li>observability engineering 把看板轉成工程決策入口。</li>
<li>unknown-unknowns 讓觀測系統要先能回答「不知道要查什麼」的問題。</li>
<li>production excellence 讓 observability 成為每個工程師的日常責任。</li>
<li>query latency 會反過來告訴你資料建模是否已經失真。</li>
<li>feature flag 配合觀測訊號，讓 production testing 可以安全推進。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">HC1</a></td>
          <td>Burn Rate 驅動可靠性</td>
          <td>把 SLO 消耗速度轉成值班與改善優先序</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/" data-link-title="Honeycomb：Production Excellence 與 Test in Production" data-link-desc="用 high-cardinality observability 把 production 變成安全的驗證環境：feature flag、progressive rollout 與即時回饋的配合。">HC2</a></td>
          <td>Production Excellence 與 Test in Prod</td>
          <td>用 observability 把 production 變成安全的驗證環境</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.honeycomb.io/resources/getting-started/what-is-observability-engineering">What Is Observability Engineering?</a>：Honeycomb 對 observability engineering 的核心定義。</li>
<li><a href="https://docs.honeycomb.io/get-started/basics/observability/concepts/high-cardinality">High Cardinality</a>：高 cardinality / dimensionality 的官方說明。</li>
<li><a href="https://docs.honeycomb.io/reference/honeycomb-ui/slos/slo-detail-view/">SLO Detail View</a>：burn rate 與 budget burndown 的產品視角。</li>
<li><a href="https://www.honeycomb.io/blog/observability-every-engineers-job-not-just-ops-problem">Observability: It&rsquo;s Every Engineer’s Job, Not Just Ops’ Problem</a>：觀測責任不只在 ops 的實踐論述。</li>
</ul>
]]></content:encoded></item><item><title>4.C13 Discord：從儲存問題回推觀測缺口</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/</guid><description>&lt;p>Discord 的儲存演進案例從觀測角度回推一個教訓：儲存成長問題通常先表現為觀測缺口。不是資料庫變慢了才去看 metric，是該有的 metric 從一開始就沒設計。每一次儲存遷移（MongoDB → Cassandra → ScyllaDB）都揭露了上一階段缺少的訊號。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Discord 處理 trillions of messages。訊息是核心 user journey — 文字、圖片、附件、thread、搜尋全部依賴訊息儲存層。從 2015 年到 2023 年，Discord 的訊息儲存經歷三代架構。&lt;/p>
&lt;p>每一代遷移都由 production 問題觸發 — 追查後發現儲存層已經撐不住，才啟動下一代架構。追查過程中反覆出現的盲區是：觀測訊號不夠早、不夠細或不夠可信。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="mongodb-階段latency-tail-不可見">MongoDB 階段：latency tail 不可見&lt;/h3>
&lt;p>早期用 MongoDB 儲存訊息。隨著使用者成長，部分大型 server（Discord 的群組概念）的訊息量遠超平均值。這些 server 的查詢 latency 偶爾飆升到秒級，但 aggregated latency metric（p50、p95）看起來正常 — 因為大型 server 的 request 數量在整體中佔比極低。&lt;/p>
&lt;p>缺少的訊號：per-server latency breakdown。aggregated metric 遮蔽了局部惡化。&lt;/p>
&lt;h3 id="cassandra-階段hot-partition-沒有早期訊號">Cassandra 階段：hot partition 沒有早期訊號&lt;/h3>
&lt;p>遷移到 Cassandra 後，partition key 設計（channel ID）讓某些高流量 channel 成為 hot partition。Cassandra 的 compaction 在 hot partition 上延遲，讀取 latency 上升。&lt;/p>
&lt;p>問題由使用者回報「訊息載入很慢」才被發現，alert 沒有提前攔截。事後回看，Cassandra 的 read latency per partition 跟 compaction pending bytes per table 這兩個 metric 都有異常，但沒有人在 dashboard 上設 alert — 因為這兩個 metric 在 Cassandra 的預設 monitoring 裡不是 first-class 告警對象。&lt;/p>
&lt;p>缺少的訊號：hot partition 識別跟 compaction health 的主動告警。&lt;/p>
&lt;h3 id="scylladb-遷移階段dual-read-沒有比對-metric">ScyllaDB 遷移階段：dual-read 沒有比對 metric&lt;/h3>
&lt;p>從 Cassandra 遷移到 ScyllaDB 的過程中，Discord 做了 dual-read（同時讀舊資料庫跟新資料庫、比對結果）。dual-read 的正確性比對有做，但 latency 跟 error rate 的比對 metric 設計不完整 — 知道結果一致，但不知道 ScyllaDB 在特定 query pattern 下是否比 Cassandra 慢。&lt;/p>
&lt;p>遷移後才發現某些 query pattern 在 ScyllaDB 上的 tail latency 比 Cassandra 高，需要額外的 schema 調整。如果 dual-read 階段就有 per-query-pattern latency comparison metric，這個問題可以在 cutover 前發現。&lt;/p>
&lt;p>缺少的訊號：migration 期間的 per-pattern latency comparison。&lt;/p>
&lt;h2 id="教訓">教訓&lt;/h2>
&lt;p>三次遷移暴露的觀測缺口有共同結構：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>缺口類型&lt;/th>
 &lt;th>MongoDB 階段&lt;/th>
 &lt;th>Cassandra 階段&lt;/th>
 &lt;th>ScyllaDB 遷移&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>維度不夠細&lt;/td>
 &lt;td>aggregated latency 遮蔽局部惡化&lt;/td>
 &lt;td>table-level metric 遮蔽 partition-level 問題&lt;/td>
 &lt;td>整體 dual-read match rate 遮蔽 per-pattern 差異&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>告警設計缺失&lt;/td>
 &lt;td>沒有 per-entity latency alert&lt;/td>
 &lt;td>沒有 hot partition alert&lt;/td>
 &lt;td>沒有 latency comparison alert&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>發現方式&lt;/td>
 &lt;td>使用者回報&lt;/td>
 &lt;td>使用者回報&lt;/td>
 &lt;td>遷移後才發現&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>共同模式：觀測訊號的粒度不夠、或告警只設在 aggregated 層 — 局部惡化被平均值淹沒，直到使用者感受到影響才被發現。&lt;/p></description><content:encoded><![CDATA[<p>Discord 的儲存演進案例從觀測角度回推一個教訓：儲存成長問題通常先表現為觀測缺口。不是資料庫變慢了才去看 metric，是該有的 metric 從一開始就沒設計。每一次儲存遷移（MongoDB → Cassandra → ScyllaDB）都揭露了上一階段缺少的訊號。</p>
<h2 id="業務背景">業務背景</h2>
<p>Discord 處理 trillions of messages。訊息是核心 user journey — 文字、圖片、附件、thread、搜尋全部依賴訊息儲存層。從 2015 年到 2023 年，Discord 的訊息儲存經歷三代架構。</p>
<p>每一代遷移都由 production 問題觸發 — 追查後發現儲存層已經撐不住，才啟動下一代架構。追查過程中反覆出現的盲區是：觀測訊號不夠早、不夠細或不夠可信。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="mongodb-階段latency-tail-不可見">MongoDB 階段：latency tail 不可見</h3>
<p>早期用 MongoDB 儲存訊息。隨著使用者成長，部分大型 server（Discord 的群組概念）的訊息量遠超平均值。這些 server 的查詢 latency 偶爾飆升到秒級，但 aggregated latency metric（p50、p95）看起來正常 — 因為大型 server 的 request 數量在整體中佔比極低。</p>
<p>缺少的訊號：per-server latency breakdown。aggregated metric 遮蔽了局部惡化。</p>
<h3 id="cassandra-階段hot-partition-沒有早期訊號">Cassandra 階段：hot partition 沒有早期訊號</h3>
<p>遷移到 Cassandra 後，partition key 設計（channel ID）讓某些高流量 channel 成為 hot partition。Cassandra 的 compaction 在 hot partition 上延遲，讀取 latency 上升。</p>
<p>問題由使用者回報「訊息載入很慢」才被發現，alert 沒有提前攔截。事後回看，Cassandra 的 read latency per partition 跟 compaction pending bytes per table 這兩個 metric 都有異常，但沒有人在 dashboard 上設 alert — 因為這兩個 metric 在 Cassandra 的預設 monitoring 裡不是 first-class 告警對象。</p>
<p>缺少的訊號：hot partition 識別跟 compaction health 的主動告警。</p>
<h3 id="scylladb-遷移階段dual-read-沒有比對-metric">ScyllaDB 遷移階段：dual-read 沒有比對 metric</h3>
<p>從 Cassandra 遷移到 ScyllaDB 的過程中，Discord 做了 dual-read（同時讀舊資料庫跟新資料庫、比對結果）。dual-read 的正確性比對有做，但 latency 跟 error rate 的比對 metric 設計不完整 — 知道結果一致，但不知道 ScyllaDB 在特定 query pattern 下是否比 Cassandra 慢。</p>
<p>遷移後才發現某些 query pattern 在 ScyllaDB 上的 tail latency 比 Cassandra 高，需要額外的 schema 調整。如果 dual-read 階段就有 per-query-pattern latency comparison metric，這個問題可以在 cutover 前發現。</p>
<p>缺少的訊號：migration 期間的 per-pattern latency comparison。</p>
<h2 id="教訓">教訓</h2>
<p>三次遷移暴露的觀測缺口有共同結構：</p>
<table>
  <thead>
      <tr>
          <th>缺口類型</th>
          <th>MongoDB 階段</th>
          <th>Cassandra 階段</th>
          <th>ScyllaDB 遷移</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>維度不夠細</td>
          <td>aggregated latency 遮蔽局部惡化</td>
          <td>table-level metric 遮蔽 partition-level 問題</td>
          <td>整體 dual-read match rate 遮蔽 per-pattern 差異</td>
      </tr>
      <tr>
          <td>告警設計缺失</td>
          <td>沒有 per-entity latency alert</td>
          <td>沒有 hot partition alert</td>
          <td>沒有 latency comparison alert</td>
      </tr>
      <tr>
          <td>發現方式</td>
          <td>使用者回報</td>
          <td>使用者回報</td>
          <td>遷移後才發現</td>
      </tr>
  </tbody>
</table>
<p>共同模式：觀測訊號的粒度不夠、或告警只設在 aggregated 層 — 局部惡化被平均值淹沒，直到使用者感受到影響才被發現。</p>
<p>三個缺口的修正方向也一致：</p>
<ol>
<li>把 entity-level metric（per-server、per-partition、per-query-pattern）從 debug-only 提升為 first-class 觀測訊號</li>
<li>在 aggregated alert 之外加 percentile 跟 tail latency alert（p99.9 而非只看 p95）</li>
<li>Migration 期間把 latency comparison 做成 per-pattern 的 real-time dashboard，不只看 overall match rate</li>
</ol>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a>：aggregated metric 遮蔽局部惡化是 data quality 問題 — 訊號存在但粒度不足以判讀。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：觀測缺口反覆出現代表 operating model 缺少「新服務上線 / 遷移時強制檢查觀測覆蓋」的 gate。</li>
<li><a href="/blog/backend/04-observability/debuggability-by-design/" data-link-title="4.19 Debuggability by Design" data-link-desc="把可診斷性前移到 API、async workflow、dependency call 與錯誤模型設計">4.19 Debuggability by Design</a>：per-entity latency breakdown 跟 migration comparison metric 應該在系統設計時就規劃，不是事故後補。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用者回報問題但 dashboard 看起來正常 — aggregated metric 可能遮蔽局部惡化</li>
<li>資料庫或儲存層偶爾變慢但找不到原因 — 可能缺少 per-entity 或 per-partition metric</li>
<li>Migration 做了 dual-read 但只比對正確性、沒比對 latency — 遷移後才發現效能回歸</li>
<li>告警設計只有 error rate 跟 aggregated latency — 缺少 tail latency 跟 entity-level alert</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://discord.com/blog/how-discord-stores-billions-of-messages">How Discord Stores Billions of Messages</a>（MongoDB → Cassandra 階段）</li>
<li><a href="https://discord.com/blog/how-discord-stores-trillions-of-messages">How Discord Stores Trillions of Messages</a>（Cassandra → ScyllaDB 階段）</li>
</ul>
]]></content:encoded></item><item><title>3.C13 Shopify：Debezium CDC over sharded MySQL</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/</guid><description>&lt;p>Shopify 的 CDC pipeline 揭露了 sharded monolith 上大規模 log-based CDC 的真實工程壓力。壓力集中在 snapshot 跟 oversized payload，穩態複製本身反而是最穩定的部分。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Shopify 的核心資料儲存是 100+ 個 MySQL shard，每個 shard 承載不同商家的交易資料。下游系統（搜尋索引、analytics、資料倉儲）需要近即時地取得資料變更。原本用 query-based 方案（內部系統 Longboat）輪詢資料庫，但隨 shard 數量跟資料量成長，輪詢的延遲跟資料庫負載壓力持續惡化。&lt;/p>
&lt;p>遷移到 log-based CDC（Debezium over Kafka Connect）後，pipeline 的穩態規模是 ~150 個 Debezium connector 跑在 12 個 Kubernetes pod、Black Friday peak 100K records/sec、P99 latency &amp;lt; 10s。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="snapshot-鎖定-read-replica">Snapshot 鎖定 read replica&lt;/h3>
&lt;p>Debezium 在初始同步（snapshot）時需要取得一致性快照。MySQL connector 的預設行為是對 read replica 取 global read lock，鎖住的時間跟表大小成正比。Shopify 的大表 snapshot 可能鎖住 read replica 數小時，影響線上查詢。&lt;/p>
&lt;p>Shopify 工程師直接向 Debezium 上游貢獻了「lock-free snapshot」機制 — 用 MySQL 的 GTID（Global Transaction ID）確保一致性，取代 global read lock。這個改動後來合併進 Debezium 主線，所有使用者都受益。&lt;/p>
&lt;h3 id="oversized-record">Oversized record&lt;/h3>
&lt;p>MySQL 的 blob / text 欄位可能產生超過 1 MB 的 CDC record。Kafka 的 message size limit（預設 1 MB）會讓這些 record 被 producer 拒絕。調大 &lt;code>max.message.bytes&lt;/code> 是一個選項，但會影響 broker 的記憶體跟 replication 效率。&lt;/p>
&lt;p>Shopify 的解法是把 oversized payload 寫到 GCS（Google Cloud Storage），CDC record 只帶 GCS pointer。Consumer 端在需要完整資料時再從 GCS 取。這個 pattern 把 Kafka 維持在「傳遞事件 metadata」的定位，大型 payload 走 object storage。&lt;/p>
&lt;h3 id="connector-故障隔離">Connector 故障隔離&lt;/h3>
&lt;p>150 個 connector 跑在 12 個 pod 上，一個 connector 的 failure（例如某個 shard 的 MySQL 做了 schema change、binlog 格式不相容）可能影響同 pod 上的其他 connector。Shopify 用 Kafka Connect 的 distributed mode + task rebalance 做故障隔離，但 rebalance 本身在 connector 數量多時有延遲。&lt;/p>
&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>Snapshot 鎖定&lt;/td>
 &lt;td>Lock-free snapshot（GTID）&lt;/td>
 &lt;td>需要 MySQL 啟用 GTID、upstream contribution 維護成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Oversized record&lt;/td>
 &lt;td>GCS pointer 替代 inline data&lt;/td>
 &lt;td>Consumer 端要多一步 GCS 讀取、增加端到端延遲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Connector 隔離&lt;/td>
 &lt;td>Distributed mode + rebalance&lt;/td>
 &lt;td>Rebalance storm 在大量 connector 時可能造成全域暫停&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高峰流量&lt;/td>
 &lt;td>12 pod K8s 部署、水平擴展&lt;/td>
 &lt;td>Pod 數量增加讓 Kafka Connect worker 的 rebalance 更複雜&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="回寫教材的連結">回寫教材的連結&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>：CDC 是 outbox pattern 的 log-based 替代方案。Shopify 的 case 揭露 CDC 的工程成本集中在 snapshot 跟 schema evolution，outbox 的成本集中在應用層 dual-write。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>：Kafka Connect / CDC 的進階主題。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>：message size limit 跟 broker 資源的關係。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;p>讀者在自己的系統看到以下訊號時，應該回讀本案例：&lt;/p></description><content:encoded><![CDATA[<p>Shopify 的 CDC pipeline 揭露了 sharded monolith 上大規模 log-based CDC 的真實工程壓力。壓力集中在 snapshot 跟 oversized payload，穩態複製本身反而是最穩定的部分。</p>
<h2 id="業務背景">業務背景</h2>
<p>Shopify 的核心資料儲存是 100+ 個 MySQL shard，每個 shard 承載不同商家的交易資料。下游系統（搜尋索引、analytics、資料倉儲）需要近即時地取得資料變更。原本用 query-based 方案（內部系統 Longboat）輪詢資料庫，但隨 shard 數量跟資料量成長，輪詢的延遲跟資料庫負載壓力持續惡化。</p>
<p>遷移到 log-based CDC（Debezium over Kafka Connect）後，pipeline 的穩態規模是 ~150 個 Debezium connector 跑在 12 個 Kubernetes pod、Black Friday peak 100K records/sec、P99 latency &lt; 10s。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="snapshot-鎖定-read-replica">Snapshot 鎖定 read replica</h3>
<p>Debezium 在初始同步（snapshot）時需要取得一致性快照。MySQL connector 的預設行為是對 read replica 取 global read lock，鎖住的時間跟表大小成正比。Shopify 的大表 snapshot 可能鎖住 read replica 數小時，影響線上查詢。</p>
<p>Shopify 工程師直接向 Debezium 上游貢獻了「lock-free snapshot」機制 — 用 MySQL 的 GTID（Global Transaction ID）確保一致性，取代 global read lock。這個改動後來合併進 Debezium 主線，所有使用者都受益。</p>
<h3 id="oversized-record">Oversized record</h3>
<p>MySQL 的 blob / text 欄位可能產生超過 1 MB 的 CDC record。Kafka 的 message size limit（預設 1 MB）會讓這些 record 被 producer 拒絕。調大 <code>max.message.bytes</code> 是一個選項，但會影響 broker 的記憶體跟 replication 效率。</p>
<p>Shopify 的解法是把 oversized payload 寫到 GCS（Google Cloud Storage），CDC record 只帶 GCS pointer。Consumer 端在需要完整資料時再從 GCS 取。這個 pattern 把 Kafka 維持在「傳遞事件 metadata」的定位，大型 payload 走 object storage。</p>
<h3 id="connector-故障隔離">Connector 故障隔離</h3>
<p>150 個 connector 跑在 12 個 pod 上，一個 connector 的 failure（例如某個 shard 的 MySQL 做了 schema change、binlog 格式不相容）可能影響同 pod 上的其他 connector。Shopify 用 Kafka Connect 的 distributed mode + task rebalance 做故障隔離，但 rebalance 本身在 connector 數量多時有延遲。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>挑戰</th>
          <th>解法</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Snapshot 鎖定</td>
          <td>Lock-free snapshot（GTID）</td>
          <td>需要 MySQL 啟用 GTID、upstream contribution 維護成本</td>
      </tr>
      <tr>
          <td>Oversized record</td>
          <td>GCS pointer 替代 inline data</td>
          <td>Consumer 端要多一步 GCS 讀取、增加端到端延遲</td>
      </tr>
      <tr>
          <td>Connector 隔離</td>
          <td>Distributed mode + rebalance</td>
          <td>Rebalance storm 在大量 connector 時可能造成全域暫停</td>
      </tr>
      <tr>
          <td>高峰流量</td>
          <td>12 pod K8s 部署、水平擴展</td>
          <td>Pod 數量增加讓 Kafka Connect worker 的 rebalance 更複雜</td>
      </tr>
  </tbody>
</table>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>：CDC 是 outbox pattern 的 log-based 替代方案。Shopify 的 case 揭露 CDC 的工程成本集中在 snapshot 跟 schema evolution，outbox 的成本集中在應用層 dual-write。</li>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：Kafka Connect / CDC 的進階主題。</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：message size limit 跟 broker 資源的關係。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>CDC snapshot 過程持續數小時、鎖住 read replica 影響線上查詢</li>
<li>CDC record size 頻繁超過 Kafka 的 message size limit</li>
<li>Kafka Connect connector 數量超過 50 個、rebalance 時間開始明顯增長</li>
<li>從 query-based 同步（輪詢）切換到 log-based CDC 的評估階段</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/capturing-every-change-shopify-sharded-monolith">Capturing Every Change From Shopify&rsquo;s Sharded Monolith</a></li>
</ul>
]]></content:encoded></item><item><title>9.C13 Disney+ Hotstar：IPL 板球決賽 1860 萬人同時直播</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/</guid><description>&lt;p>這個案例的核心責任是說明「全球大型直播」的容量設計 — 跟 Prime Day 同屬「可預期極端峰值」、但形狀完全不同：Prime Day 是分散全球的購物峰值、Hotstar IPL 是 &lt;em>單一時間點 + 高度集中地理區&lt;/em> 的直播峰值。容量規劃的挑戰在於 CDN、串流伺服器、live encoder、message queue 同時 saturate。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Hotstar IPL 直播的關鍵數字（引自 &lt;a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">Hotstar global record&lt;/a>）：&lt;/p>
&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>1860 萬 人（2021-03 IPL 決賽）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>全球記錄&lt;/td>
 &lt;td>該時點全球同時觀看直播的最高記錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AWS Media Services + AWS CloudFront&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶基礎&lt;/td>
 &lt;td>印度為主、跨亞洲&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>AWS Media Services 在大型事件的歷史記錄：Olympics、Super Bowl、IPL Cricket（引自 &lt;a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">AWS large-scale streaming events&lt;/a>）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Hotstar 案例揭露三個全球直播容量重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>集中地理區 = CDN 壓力集中&lt;/strong>：Prime Day 的流量分散全球、單一地區 CDN 不會 saturate；IPL 主要觀眾在印度、所有印度 PoP 同一時間 saturate。CDN 容量規劃必須按地區獨立做、不能用「全球總容量」當保證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的 cardinality 與地區訊號治理、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的「地理分片容量」。&lt;/li>
&lt;li>&lt;strong>直播跟 VoD 是不同容量問題&lt;/strong>：VoD 觀眾分散時間、CDN 可預先 cache；直播觀眾集中時間、每一個 manifest / segment 都是 live 拉取、cache hit 反而是危險（拉到舊的 segment）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache freshness boundary、跟 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列&lt;/a> 的 fan-out 設計。&lt;/li>
&lt;li>&lt;strong>多 bitrate 動態切換 = 真實容量是 bitrate 加權&lt;/strong>：1860 萬觀眾不是都看 1080p — 印度行動網路下大多看 720p 或 480p、bitrate 加權後的 total bandwidth 可能比想像低。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的真實 workload shape。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「1860 萬同時觀看」是 &lt;em>峰值瞬間&lt;/em>、不是全程平均。決賽 4 小時、觀眾數呈鐘形曲線、峰值維持時間可能只有 10-30 分鐘（比賽關鍵時刻）。容量規劃要看峰值持續時間、不只看峰值高度。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>CDN 容量規劃按地理區分割&lt;/strong>：不要假設「全球 CDN 總量」夠用、要按主要觀眾分布的地區做容量保證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>直播必須 pre-scaling、不能依賴 reactive&lt;/strong>：直播開始之後 CDN reactive 擴容已經太晚、觀眾體驗已壞。事件型 scheduled scaling + over-provisioning 是必須。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a>。&lt;/li>
&lt;li>&lt;strong>multi-bitrate / ABR streaming 是容量緩衝&lt;/strong>：當網路擁塞、player 自動降 bitrate、總頻寬壓力下降。這層降級是隱性容量緩衝、要在壓測時驗證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 saturation 行為。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP CDN + Media CDN、Azure Front Door + Media Services、Akamai / Cloudflare / Fastly 等 multi-CDN 都是對等候選。差異是 PoP 地理分布跟 manifest 處理能力。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「全球大型直播」的容量設計 — 跟 Prime Day 同屬「可預期極端峰值」、但形狀完全不同：Prime Day 是分散全球的購物峰值、Hotstar IPL 是 <em>單一時間點 + 高度集中地理區</em> 的直播峰值。容量規劃的挑戰在於 CDN、串流伺服器、live encoder、message queue 同時 saturate。</p>
<h2 id="觀察">觀察</h2>
<p>Hotstar IPL 直播的關鍵數字（引自 <a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">Hotstar global record</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同時觀看峰值</td>
          <td>1860 萬 人（2021-03 IPL 決賽）</td>
      </tr>
      <tr>
          <td>全球記錄</td>
          <td>該時點全球同時觀看直播的最高記錄</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AWS Media Services + AWS CloudFront</td>
      </tr>
      <tr>
          <td>客戶基礎</td>
          <td>印度為主、跨亞洲</td>
      </tr>
  </tbody>
</table>
<p>AWS Media Services 在大型事件的歷史記錄：Olympics、Super Bowl、IPL Cricket（引自 <a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">AWS large-scale streaming events</a>）。</p>
<h2 id="判讀">判讀</h2>
<p>Hotstar 案例揭露三個全球直播容量重點。</p>
<ol>
<li><strong>集中地理區 = CDN 壓力集中</strong>：Prime Day 的流量分散全球、單一地區 CDN 不會 saturate；IPL 主要觀眾在印度、所有印度 PoP 同一時間 saturate。CDN 容量規劃必須按地區獨立做、不能用「全球總容量」當保證。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 cardinality 與地區訊號治理、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的「地理分片容量」。</li>
<li><strong>直播跟 VoD 是不同容量問題</strong>：VoD 觀眾分散時間、CDN 可預先 cache；直播觀眾集中時間、每一個 manifest / segment 都是 live 拉取、cache hit 反而是危險（拉到舊的 segment）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache freshness boundary、跟 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a> 的 fan-out 設計。</li>
<li><strong>多 bitrate 動態切換 = 真實容量是 bitrate 加權</strong>：1860 萬觀眾不是都看 1080p — 印度行動網路下大多看 720p 或 480p、bitrate 加權後的 total bandwidth 可能比想像低。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的真實 workload shape。</li>
</ol>
<p>需要警惕：「1860 萬同時觀看」是 <em>峰值瞬間</em>、不是全程平均。決賽 4 小時、觀眾數呈鐘形曲線、峰值維持時間可能只有 10-30 分鐘（比賽關鍵時刻）。容量規劃要看峰值持續時間、不只看峰值高度。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>CDN 容量規劃按地理區分割</strong>：不要假設「全球 CDN 總量」夠用、要按主要觀眾分布的地區做容量保證。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>直播必須 pre-scaling、不能依賴 reactive</strong>：直播開始之後 CDN reactive 擴容已經太晚、觀眾體驗已壞。事件型 scheduled scaling + over-provisioning 是必須。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
<li><strong>multi-bitrate / ABR streaming 是容量緩衝</strong>：當網路擁塞、player 自動降 bitrate、總頻寬壓力下降。這層降級是隱性容量緩衝、要在壓測時驗證。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 saturation 行為。</li>
</ol>
<p>跨平台等效：GCP CDN + Media CDN、Azure Front Door + Media Services、Akamai / Cloudflare / Fastly 等 multi-CDN 都是對等候選。差異是 PoP 地理分布跟 manifest 處理能力。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃全球直播 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做 CDN 容量設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a></li>
<li>想理解 cache freshness 在直播的影響 → <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a></li>
<li>對照其他可預期峰值 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a>（分散全球的峰值）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">In the news: Hotstar sets new global record for live viewership</a></li>
<li><a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">Large scale streaming events on AWS</a></li>
<li><a href="https://aws.amazon.com/media/direct-to-consumer-d2c-streaming/">Direct to Consumer &amp; Streaming on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>Google：Toil Budget 與 Automation 投資政策</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/</guid><description>&lt;p>Toil budget 的核心責任是把重複手動工作變成可治理成本。Google SRE 的關鍵做法是先量化 toil，再把超額部分強制導向自動化投資，而不是持續靠人力吸收。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>許多團隊的可靠性工作會被 incident handling 與手動修復吃掉。短期看似把事情解決，長期會造成兩個後果：一是 on-call 壓力升高，二是系統問題持續累積。沒有 toil budget 時，團隊很難判斷何時該停止加功能、先補工程基礎。&lt;/p>
&lt;h2 id="決策機制">決策機制&lt;/h2>
&lt;p>Toil budget 是把工時結果接到 release 與 backlog 決策的機制，單純統計工時只完成一半。&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>Toil 分類&lt;/td>
 &lt;td>哪些工作屬於可自動化 toil&lt;/td>
 &lt;td>toil taxonomy&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>時間配比&lt;/td>
 &lt;td>toil 比例是否超過可承受區&lt;/td>
 &lt;td>budget 門檻（例如 50%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>超標處理&lt;/td>
 &lt;td>超標後怎麼調整優先序&lt;/td>
 &lt;td>凍結部分 feature、轉投自動化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>改善驗證&lt;/td>
 &lt;td>自動化是否真的回收工時&lt;/td>
 &lt;td>closure 指標與 evidence&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>toil ratio&lt;/td>
 &lt;td>是否長期超出預算&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>incident manual-step count&lt;/td>
 &lt;td>事故處理是否過度依賴人工&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>automation closure rate&lt;/td>
 &lt;td>改善項是否真的落地&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>on-call overload signal&lt;/td>
 &lt;td>值班負荷是否持續上升&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>最常見錯誤是把 toil 視為「正常運維工作」，結果讓超標狀態常態化。另一個錯誤是只記錄工時，不把結果接到 release gate 與優先序調整。這兩種做法都會讓可靠性債繼續滾大。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>把 toil budget 落地時，先在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Reliability Debt Backlog&lt;/a> 建立分類與排序，再把超標條件接到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate&lt;/a>。事後改善要回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://sre.google/sre-book/table-of-contents/">Google SRE Book&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://sre.google/workbook/table-of-contents/">Google SRE Workbook&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Toil budget 的核心責任是把重複手動工作變成可治理成本。Google SRE 的關鍵做法是先量化 toil，再把超額部分強制導向自動化投資，而不是持續靠人力吸收。</p>
<h2 id="問題場景">問題場景</h2>
<p>許多團隊的可靠性工作會被 incident handling 與手動修復吃掉。短期看似把事情解決，長期會造成兩個後果：一是 on-call 壓力升高，二是系統問題持續累積。沒有 toil budget 時，團隊很難判斷何時該停止加功能、先補工程基礎。</p>
<h2 id="決策機制">決策機制</h2>
<p>Toil budget 是把工時結果接到 release 與 backlog 決策的機制，單純統計工時只完成一半。</p>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>實際輸出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Toil 分類</td>
          <td>哪些工作屬於可自動化 toil</td>
          <td>toil taxonomy</td>
      </tr>
      <tr>
          <td>時間配比</td>
          <td>toil 比例是否超過可承受區</td>
          <td>budget 門檻（例如 50%）</td>
      </tr>
      <tr>
          <td>超標處理</td>
          <td>超標後怎麼調整優先序</td>
          <td>凍結部分 feature、轉投自動化</td>
      </tr>
      <tr>
          <td>改善驗證</td>
          <td>自動化是否真的回收工時</td>
          <td>closure 指標與 evidence</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>toil ratio</td>
          <td>是否長期超出預算</td>
          <td><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a></td>
      </tr>
      <tr>
          <td>incident manual-step count</td>
          <td>事故處理是否過度依賴人工</td>
          <td><a href="/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16</a></td>
      </tr>
      <tr>
          <td>automation closure rate</td>
          <td>改善項是否真的落地</td>
          <td><a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a></td>
      </tr>
      <tr>
          <td>on-call overload signal</td>
          <td>值班負荷是否持續上升</td>
          <td><a href="/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>最常見錯誤是把 toil 視為「正常運維工作」，結果讓超標狀態常態化。另一個錯誤是只記錄工時，不把結果接到 release gate 與優先序調整。這兩種做法都會讓可靠性債繼續滾大。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>把 toil budget 落地時，先在 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Reliability Debt Backlog</a> 建立分類與排序，再把超標條件接到 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a>。事後改善要回寫 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://sre.google/sre-book/table-of-contents/">Google SRE Book</a></li>
<li><a href="https://sre.google/workbook/table-of-contents/">Google SRE Workbook</a></li>
</ul>
]]></content:encoded></item><item><title>Discord</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/</guid><description>&lt;p>Discord 是大規模長連線 gateway 的代表、事故多源自 capacity surprise（用戶行為意外觸發 fan-out 放大）。Discord engineering blog 揭露多次 scaling 事故。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Long-lived WebSocket：與短連線 HTTP 服務的故障模式差異&lt;/li>
&lt;li>Fan-out 放大：單一訊息推播到大量連線的容量風險&lt;/li>
&lt;li>Sharding 與 cluster topology：超大型 guild 的特殊處理&lt;/li>
&lt;li>Gradual rollout 限制：長連線服務的 deploy 節奏&lt;/li>
&lt;/ul>
&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>2023&lt;/td>
 &lt;td>Authentication outage&lt;/td>
 &lt;td>capacity surprise、reconnection&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2026&lt;/td>
 &lt;td>Voice outage&lt;/td>
 &lt;td>session state 規模化的失敗模式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Discord 這個案例在講的是長連線與 session state 一旦失衡，事故就會直接反映在使用者連線體感上。讀者先看懂 Gateway、authentication 與 voice 這些路由的責任，再把 reconnection storm 視為核心風險。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 gateway 或 session 基礎設施出現問題時，復原順序必須同時照顧連線穩定與服務容量。當流量重新接回來時，先保住重連與驗證，再處理後續聊天與 voice 路徑，能減少二次抖動。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否看出問題在連線層還是 session state&lt;/li>
&lt;li>能否把 capacity surprise 轉成可預測的壓力模型&lt;/li>
&lt;li>能否讓 reconnection path 比一般流量更早恢復&lt;/li>
&lt;li>能否把 gateway 事故寫成客戶體感可理解的時間線&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Discord 和 Slack 是兩種不同的長連線通訊平台，但都會遇到 reconnection 與 status communication 問題。它也可和 Heroku 一起讀，因為多租戶入口與 session state 一旦不穩，故障就會直接表現在使用者連線上。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2023 authentication outage 是連線層與驗證路徑失衡的樣本。&lt;/li>
&lt;li>2026 voice outage 則展示 session state 與 voice path 的恢復難度。&lt;/li>
&lt;li>reconnect storm 是長連線平台事故的常見擴散器。&lt;/li>
&lt;li>gateway 與 voice path 的分工會直接影響恢復順序。&lt;/li>
&lt;li>shard topology 會決定大型 guild 的故障擴散方式。&lt;/li>
&lt;li>long-lived WebSocket 讓 gradual rollout 的風險比短連線服務更高。&lt;/li>
&lt;li>authentication 與 voice path 分層，讓不同失效能有不同恢復路徑。&lt;/li>
&lt;li>capacity surprise 讓平時看似正常的流量，在事故時突然失控。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">DC1&lt;/a>&lt;/td>
 &lt;td>Gateway 容量事件&lt;/td>
 &lt;td>在長連線平台中控制回復造成的二次擁塞&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.discord.com/developers/events/gateway">Gateway&lt;/a>：Discord Gateway 的官方文檔，補 long-lived WebSocket 語意。&lt;/li>
&lt;li>&lt;a href="https://discord.com/blog/authentication-outage">25% or 6 to 4: The 11/6/23 Authentication Outage&lt;/a>：Discord 服務中斷的技術回顧。&lt;/li>
&lt;li>&lt;a href="https://discord.com/blog/behind-the-scenes-of-the-3-25-26-voice-outage">You’ve Got (Too Much) Mail: Behind the Scenes of the 3/25/26 Voice Outage&lt;/a>：Discord 最近的 voice outage 回顧。&lt;/li>
&lt;li>&lt;a href="https://discord.com/blog">Discord Blog&lt;/a>：Discord engineering 與 outage 類文章總入口。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Discord 是大規模長連線 gateway 的代表、事故多源自 capacity surprise（用戶行為意外觸發 fan-out 放大）。Discord engineering blog 揭露多次 scaling 事故。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Long-lived WebSocket：與短連線 HTTP 服務的故障模式差異</li>
<li>Fan-out 放大：單一訊息推播到大量連線的容量風險</li>
<li>Sharding 與 cluster topology：超大型 guild 的特殊處理</li>
<li>Gradual rollout 限制：長連線服務的 deploy 節奏</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2023</td>
          <td>Authentication outage</td>
          <td>capacity surprise、reconnection</td>
      </tr>
      <tr>
          <td>2026</td>
          <td>Voice outage</td>
          <td>session state 規模化的失敗模式</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Discord 這個案例在講的是長連線與 session state 一旦失衡，事故就會直接反映在使用者連線體感上。讀者先看懂 Gateway、authentication 與 voice 這些路由的責任，再把 reconnection storm 視為核心風險。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 gateway 或 session 基礎設施出現問題時，復原順序必須同時照顧連線穩定與服務容量。當流量重新接回來時，先保住重連與驗證，再處理後續聊天與 voice 路徑，能減少二次抖動。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否看出問題在連線層還是 session state</li>
<li>能否把 capacity surprise 轉成可預測的壓力模型</li>
<li>能否讓 reconnection path 比一般流量更早恢復</li>
<li>能否把 gateway 事故寫成客戶體感可理解的時間線</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Discord 和 Slack 是兩種不同的長連線通訊平台，但都會遇到 reconnection 與 status communication 問題。它也可和 Heroku 一起讀，因為多租戶入口與 session state 一旦不穩，故障就會直接表現在使用者連線上。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2023 authentication outage 是連線層與驗證路徑失衡的樣本。</li>
<li>2026 voice outage 則展示 session state 與 voice path 的恢復難度。</li>
<li>reconnect storm 是長連線平台事故的常見擴散器。</li>
<li>gateway 與 voice path 的分工會直接影響恢復順序。</li>
<li>shard topology 會決定大型 guild 的故障擴散方式。</li>
<li>long-lived WebSocket 讓 gradual rollout 的風險比短連線服務更高。</li>
<li>authentication 與 voice path 分層，讓不同失效能有不同恢復路徑。</li>
<li>capacity surprise 讓平時看似正常的流量，在事故時突然失控。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">DC1</a></td>
          <td>Gateway 容量事件</td>
          <td>在長連線平台中控制回復造成的二次擁塞</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://docs.discord.com/developers/events/gateway">Gateway</a>：Discord Gateway 的官方文檔，補 long-lived WebSocket 語意。</li>
<li><a href="https://discord.com/blog/authentication-outage">25% or 6 to 4: The 11/6/23 Authentication Outage</a>：Discord 服務中斷的技術回顧。</li>
<li><a href="https://discord.com/blog/behind-the-scenes-of-the-3-25-26-voice-outage">You’ve Got (Too Much) Mail: Behind the Scenes of the 3/25/26 Voice Outage</a>：Discord 最近的 voice outage 回顧。</li>
<li><a href="https://discord.com/blog">Discord Blog</a>：Discord engineering 與 outage 類文章總入口。</li>
</ul>
]]></content:encoded></item><item><title>Microsoft / Azure SRE</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/</guid><description>&lt;p>Microsoft Azure 的 SRE 文章與 Resilience patterns 文件是大型雲端供應商的可靠性工程公開素材。教學重點在「企業導向 cloud 的可靠性 patterns 與 governance」。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Azure Well-Architected Framework：Reliability pillar 的設計指導&lt;/li>
&lt;li>Resilience patterns：retry、circuit breaker、bulkhead 的官方範例&lt;/li>
&lt;li>Site Reliability Engineering at Microsoft：內部 SRE 組織與實踐&lt;/li>
&lt;li>Compliance-driven reliability：企業客戶要求下的可靠性 SLA&lt;/li>
&lt;/ul>
&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>Well-Architected Framework&lt;/td>
 &lt;td>Reliability pillar 結構與審查流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resilience Design Patterns&lt;/td>
 &lt;td>retry / breaker / bulkhead 等實作範例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Azure SRE Engineering&lt;/td>
 &lt;td>Microsoft 內部 SRE 演化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Chaos Studio&lt;/td>
 &lt;td>Azure 平台原生 chaos 工具&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Microsoft 這個案例在講的是企業雲端如何把可靠性寫進架構規範與設計模式。讀者先抓 reliability pillar、self-healing 與 design patterns 的分工，再把它們視為治理語言，而不是單純的文件清單。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當服務要面對企業客戶的 SLA 要求時，先看設計模式能否對應 failure mode，再看治理流程是否能把 pattern 真的落到架構審查。當團隊需要做 retry 或 bulkhead 時，重點是能不能選到正確的位置與層級。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否從 failure mode 反推適合的 reliability pattern&lt;/li>
&lt;li>能否把 self-healing 寫成可驗證的設計要求&lt;/li>
&lt;li>能否把架構審查和 SLA 約束對齊&lt;/li>
&lt;li>能否把 Azure SRE 實踐轉成團隊可用的治理語言&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Microsoft 這頁和 Stripe、Google 的差異在於它更偏治理與設計審查，而不是單一事故。讀者若先懂這頁，再看 Azure AD 和 M365，就能把 identity 失效與企業雲端的 reliability pattern 串成同一條理解路徑。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>self-healing 把故障轉成可恢復的設計要求，而不是單靠人工補救。&lt;/li>
&lt;li>reliability pillar 讓團隊在架構審查時就對齊失效模式與補救方式。&lt;/li>
&lt;li>retry / circuit breaker / bulkhead 提供可重複使用的設計模式。&lt;/li>
&lt;li>compliance-driven reliability 把 SLA 約束寫進雲端治理。&lt;/li>
&lt;li>chaos studio 讓雲端平台本身提供測試失效的工具。&lt;/li>
&lt;li>Well-Architected Framework 讓可靠性審查變成標準流程。&lt;/li>
&lt;li>health check / retry policy 讓應用層能和平台層恢復節奏對齊。&lt;/li>
&lt;li>governance 語言把企業 SLA 與技術決策連起來。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">MS1&lt;/a>&lt;/td>
 &lt;td>變更治理與可靠性門檻&lt;/td>
 &lt;td>以風險分層與 release gate 降低系統性回歸&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/" data-link-title="Microsoft：Safe Deployment Practices 與 Resilience Patterns" data-link-desc="大型 SaaS 用 ring-based deployment 控制變更擴散，用標準化 resilience patterns 讓依賴失效時的降級行為可預測。">MS2&lt;/a>&lt;/td>
 &lt;td>Safe Deployment Practices 與 Resilience Patterns&lt;/td>
 &lt;td>ring-based deployment 與標準化韌性設計模式的制度化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/">Azure Architecture Center&lt;/a>：Azure 架構中心總入口。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/resiliency/overview">Reliability quick links&lt;/a>：Azure Well-Architected Reliability 入口。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/self-healing">Design for self-healing&lt;/a>：self-healing 與 failover 的官方設計原則。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-gb/azure/well-architected/reliability/design-patterns">Architecture design patterns that support reliability&lt;/a>：可靠性設計模式總覽。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Microsoft Azure 的 SRE 文章與 Resilience patterns 文件是大型雲端供應商的可靠性工程公開素材。教學重點在「企業導向 cloud 的可靠性 patterns 與 governance」。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Azure Well-Architected Framework：Reliability pillar 的設計指導</li>
<li>Resilience patterns：retry、circuit breaker、bulkhead 的官方範例</li>
<li>Site Reliability Engineering at Microsoft：內部 SRE 組織與實踐</li>
<li>Compliance-driven reliability：企業客戶要求下的可靠性 SLA</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Well-Architected Framework</td>
          <td>Reliability pillar 結構與審查流程</td>
      </tr>
      <tr>
          <td>Resilience Design Patterns</td>
          <td>retry / breaker / bulkhead 等實作範例</td>
      </tr>
      <tr>
          <td>Azure SRE Engineering</td>
          <td>Microsoft 內部 SRE 演化</td>
      </tr>
      <tr>
          <td>Chaos Studio</td>
          <td>Azure 平台原生 chaos 工具</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Microsoft 這個案例在講的是企業雲端如何把可靠性寫進架構規範與設計模式。讀者先抓 reliability pillar、self-healing 與 design patterns 的分工，再把它們視為治理語言，而不是單純的文件清單。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當服務要面對企業客戶的 SLA 要求時，先看設計模式能否對應 failure mode，再看治理流程是否能把 pattern 真的落到架構審查。當團隊需要做 retry 或 bulkhead 時，重點是能不能選到正確的位置與層級。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否從 failure mode 反推適合的 reliability pattern</li>
<li>能否把 self-healing 寫成可驗證的設計要求</li>
<li>能否把架構審查和 SLA 約束對齊</li>
<li>能否把 Azure SRE 實踐轉成團隊可用的治理語言</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Microsoft 這頁和 Stripe、Google 的差異在於它更偏治理與設計審查，而不是單一事故。讀者若先懂這頁，再看 Azure AD 和 M365，就能把 identity 失效與企業雲端的 reliability pattern 串成同一條理解路徑。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>self-healing 把故障轉成可恢復的設計要求，而不是單靠人工補救。</li>
<li>reliability pillar 讓團隊在架構審查時就對齊失效模式與補救方式。</li>
<li>retry / circuit breaker / bulkhead 提供可重複使用的設計模式。</li>
<li>compliance-driven reliability 把 SLA 約束寫進雲端治理。</li>
<li>chaos studio 讓雲端平台本身提供測試失效的工具。</li>
<li>Well-Architected Framework 讓可靠性審查變成標準流程。</li>
<li>health check / retry policy 讓應用層能和平台層恢復節奏對齊。</li>
<li>governance 語言把企業 SLA 與技術決策連起來。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">MS1</a></td>
          <td>變更治理與可靠性門檻</td>
          <td>以風險分層與 release gate 降低系統性回歸</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/" data-link-title="Microsoft：Safe Deployment Practices 與 Resilience Patterns" data-link-desc="大型 SaaS 用 ring-based deployment 控制變更擴散，用標準化 resilience patterns 讓依賴失效時的降級行為可預測。">MS2</a></td>
          <td>Safe Deployment Practices 與 Resilience Patterns</td>
          <td>ring-based deployment 與標準化韌性設計模式的制度化</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/">Azure Architecture Center</a>：Azure 架構中心總入口。</li>
<li><a href="https://learn.microsoft.com/en-us/azure/well-architected/resiliency/overview">Reliability quick links</a>：Azure Well-Architected Reliability 入口。</li>
<li><a href="https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/self-healing">Design for self-healing</a>：self-healing 與 failover 的官方設計原則。</li>
<li><a href="https://learn.microsoft.com/en-gb/azure/well-architected/reliability/design-patterns">Architecture design patterns that support reliability</a>：可靠性設計模式總覽。</li>
</ul>
]]></content:encoded></item><item><title>4.C14 觀測平台成本治理：從帳單驚嚇到可預測成本</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/observability-cost-governance-at-scale/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/observability-cost-governance-at-scale/</guid><description>&lt;p>觀測成本治理案例來自多家企業的共同經驗：觀測平台帳單每季成長 30%，管理層問「為什麼監控這麼貴」但沒人能歸因。問題的核心不是「花太多」而是「花在哪不知道」— 沒有 per-team cost attribution 的觀測平台，成本優化只能靠全域砍 retention 或降 sampling，兩者都會傷害觀測品質。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>這個案例綜合三個組織的經驗模式：&lt;/p>
&lt;p>一家中型 SaaS 公司用 Datadog 做全端觀測（APM + logs + metrics + RUM）。月帳單從 $15K 成長到 $60K，兩年內四倍。CFO 問 CTO「這筆錢買到什麼」，CTO 轉問 platform team，platform team 說不出哪些團隊佔多少。&lt;/p>
&lt;p>一家金融科技公司自建 Grafana Stack（Prometheus + Loki + Tempo + Mimir）。自建沒有 SaaS 帳單，但 Kubernetes 節點跟 storage 的成本持續增加。infra team 知道 Mimir 的 storage 在成長，但不知道是哪些 metric label 造成的 cardinality 爆炸。&lt;/p>
&lt;p>一家遊戲公司用 CloudWatch 做 AWS 原生觀測。Logs 的 ingestion 費用佔帳單 70%，但追查後發現 90% 是 debug-level log，只在排錯時用到，平常沒人查。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="沒有-cost-attribution">沒有 cost attribution&lt;/h3>
&lt;p>觀測帳單通常是 organization-level 的一筆支出。SaaS 帳單按 hosts、custom metrics、log volume、APM spans 計費；自建平台按 compute 跟 storage 計費。兩種模式都缺少「這些費用是哪個 team / service 造成的」的歸因。&lt;/p>
&lt;p>沒有 attribution 的後果是所有優化都是全域操作 — 砍 retention 從 30 天到 7 天影響所有人，降 sampling 從 100% 到 10% 影響所有服務。需要觀測資料的團隊被平均到成本節省裡，不需要的團隊搭便車。&lt;/p>
&lt;h3 id="cardinality-爆炸">Cardinality 爆炸&lt;/h3>
&lt;p>Metrics 成本的主要 driver 是 cardinality — unique label combination 的數量。常見的 cardinality 爆炸來源：&lt;/p>
&lt;ul>
&lt;li>把 user ID 或 request ID 放進 metric label（每個 unique user 產生一組 series）&lt;/li>
&lt;li>動態的 endpoint path（&lt;code>/api/users/123&lt;/code> 每個 user ID 是一個 label value）&lt;/li>
&lt;li>多租戶 label 過細（tenant × region × service × endpoint 的笛卡兒積）&lt;/li>
&lt;/ul>
&lt;p>一個失控的 label 可以讓 series 數量從 10 萬跳到 1000 萬。SaaS 的計費是 per custom metric，自建的代價是 Prometheus / Mimir 的 memory 跟 storage。&lt;/p>
&lt;h3 id="log-volume-失控">Log volume 失控&lt;/h3>
&lt;p>Debug-level log 在開發階段有用，但 production 環境裡通常只在排錯時被查。全量 debug log 送進 hot tier（Elasticsearch、Loki、CloudWatch Logs）的 ingestion 跟 storage 成本是最大的 log 成本來源。&lt;/p>
&lt;p>問題是沒人敢降 debug log — 「萬一出事需要 debug log 怎麼辦」。恐懼驅動的 log level 設定讓 log volume 只升不降。&lt;/p></description><content:encoded><![CDATA[<p>觀測成本治理案例來自多家企業的共同經驗：觀測平台帳單每季成長 30%，管理層問「為什麼監控這麼貴」但沒人能歸因。問題的核心不是「花太多」而是「花在哪不知道」— 沒有 per-team cost attribution 的觀測平台，成本優化只能靠全域砍 retention 或降 sampling，兩者都會傷害觀測品質。</p>
<h2 id="業務背景">業務背景</h2>
<p>這個案例綜合三個組織的經驗模式：</p>
<p>一家中型 SaaS 公司用 Datadog 做全端觀測（APM + logs + metrics + RUM）。月帳單從 $15K 成長到 $60K，兩年內四倍。CFO 問 CTO「這筆錢買到什麼」，CTO 轉問 platform team，platform team 說不出哪些團隊佔多少。</p>
<p>一家金融科技公司自建 Grafana Stack（Prometheus + Loki + Tempo + Mimir）。自建沒有 SaaS 帳單，但 Kubernetes 節點跟 storage 的成本持續增加。infra team 知道 Mimir 的 storage 在成長，但不知道是哪些 metric label 造成的 cardinality 爆炸。</p>
<p>一家遊戲公司用 CloudWatch 做 AWS 原生觀測。Logs 的 ingestion 費用佔帳單 70%，但追查後發現 90% 是 debug-level log，只在排錯時用到，平常沒人查。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="沒有-cost-attribution">沒有 cost attribution</h3>
<p>觀測帳單通常是 organization-level 的一筆支出。SaaS 帳單按 hosts、custom metrics、log volume、APM spans 計費；自建平台按 compute 跟 storage 計費。兩種模式都缺少「這些費用是哪個 team / service 造成的」的歸因。</p>
<p>沒有 attribution 的後果是所有優化都是全域操作 — 砍 retention 從 30 天到 7 天影響所有人，降 sampling 從 100% 到 10% 影響所有服務。需要觀測資料的團隊被平均到成本節省裡，不需要的團隊搭便車。</p>
<h3 id="cardinality-爆炸">Cardinality 爆炸</h3>
<p>Metrics 成本的主要 driver 是 cardinality — unique label combination 的數量。常見的 cardinality 爆炸來源：</p>
<ul>
<li>把 user ID 或 request ID 放進 metric label（每個 unique user 產生一組 series）</li>
<li>動態的 endpoint path（<code>/api/users/123</code> 每個 user ID 是一個 label value）</li>
<li>多租戶 label 過細（tenant × region × service × endpoint 的笛卡兒積）</li>
</ul>
<p>一個失控的 label 可以讓 series 數量從 10 萬跳到 1000 萬。SaaS 的計費是 per custom metric，自建的代價是 Prometheus / Mimir 的 memory 跟 storage。</p>
<h3 id="log-volume-失控">Log volume 失控</h3>
<p>Debug-level log 在開發階段有用，但 production 環境裡通常只在排錯時被查。全量 debug log 送進 hot tier（Elasticsearch、Loki、CloudWatch Logs）的 ingestion 跟 storage 成本是最大的 log 成本來源。</p>
<p>問題是沒人敢降 debug log — 「萬一出事需要 debug log 怎麼辦」。恐懼驅動的 log level 設定讓 log volume 只升不降。</p>
<h3 id="trace-sampling-恐懼">Trace sampling 恐懼</h3>
<p>類似的恐懼存在於 trace sampling — 「如果剛好那筆有問題的 request 被 sample 掉怎麼辦」。100% tracing 的成本在中等規模（每秒數萬 request）就開始顯著。</p>
<h2 id="解法">解法</h2>
<h3 id="cost-attribution-by-team--service">Cost attribution by team / service</h3>
<p>第一步是讓成本可見，歸因先於優化。</p>
<p>SaaS 平台：用 Datadog 的 usage attribution 或 Grafana Cloud 的 usage reporting 把 ingestion 按 service tag / team tag 拆分。每個 team 看到自己的 metric series、log volume 跟 span 數量。</p>
<p>自建平台：在 Mimir / Loki 的 tenant 維度或 Prometheus 的 namespace 維度拆分 storage 跟 query cost。用 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a> 的框架把 infra cost 按 service ownership 分配。</p>
<p>Attribution 本身就能驅動行為改變 — 當團隊看到自己佔了 40% 的 log volume、而且 95% 是 debug level 時，他們會主動調 log level。</p>
<h3 id="cardinality-budget-per-team">Cardinality budget per team</h3>
<p>Attribution 之後，為每個 team / service 設定 cardinality budget（active series 上限）。超出 budget 的 series 進入 review 流程 — team 決定哪些 label 可以 aggregate 或移除，而非由 platform 單方面 drop。</p>
<p>Budget 的設定依據是 baseline measurement + growth rate，不是拍腦袋。先觀察 3 個月的 cardinality 趨勢，把 budget 設在 baseline 的 1.5 倍，每季 review。</p>
<h3 id="log-tiering">Log tiering</h3>
<p>把 log 從「全部進 hot tier」改成分層：</p>
<table>
  <thead>
      <tr>
          <th>Log level</th>
          <th>目的地</th>
          <th>Retention</th>
          <th>查詢延遲</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error / Warn</td>
          <td>Hot tier（Loki / Elasticsearch）</td>
          <td>30 天</td>
          <td>即時</td>
      </tr>
      <tr>
          <td>Info</td>
          <td>Warm tier（壓縮 + 延遲查詢）</td>
          <td>14 天</td>
          <td>秒到分鐘</td>
      </tr>
      <tr>
          <td>Debug</td>
          <td>Cold archive（object storage）</td>
          <td>7 天</td>
          <td>分鐘到小時</td>
      </tr>
  </tbody>
</table>
<p>Debug log 仍然保留，但不進昂貴的 hot tier。需要排錯時從 cold archive 拉回 — 多等幾分鐘的代價遠低於全量 hot tier 的持續成本。</p>
<h3 id="adaptive-sampling">Adaptive sampling</h3>
<p>Trace sampling 從 uniform 改成 adaptive：</p>
<ul>
<li>錯誤 request 100% 保留</li>
<li>高 latency request（&gt; p99）100% 保留</li>
<li>正常 request 依 traffic volume adaptive sampling（高流量 endpoint 低 sample rate、低流量 endpoint 高 sample rate）</li>
</ul>
<p>Adaptive sampling 保留了排錯最需要的 trace（error 跟 outlier），砍的是正常 request 的重複 trace。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>不治理</th>
          <th>治理後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>成本趨勢</td>
          <td>隨 traffic 超線性成長</td>
          <td>跟 traffic 線性成長或低於線性</td>
      </tr>
      <tr>
          <td>觀測覆蓋</td>
          <td>全量（但可能是低品質的全量）</td>
          <td>分層（high-value 資料保留全量、low-value 降級）</td>
      </tr>
      <tr>
          <td>Debug 體驗</td>
          <td>所有資料都在 hot tier、查得快</td>
          <td>部分資料要從 cold archive 拉、多等幾分鐘</td>
      </tr>
      <tr>
          <td>團隊自主性</td>
          <td>無限制（cardinality 跟 log level 隨意）</td>
          <td>有 budget 跟 policy 約束</td>
      </tr>
      <tr>
          <td>治理人力</td>
          <td>零（直到帳單爆炸才開始）</td>
          <td>需要 platform team 持續維護 attribution + budget + policy</td>
      </tr>
  </tbody>
</table>
<p>治理的最大風險是「砍過頭」— 在事故期間發現 debug log 被移到 cold archive 查不到、或 trace 被 sample 掉找不到問題 request。Adaptive sampling 跟 error retention 100% 是安全網，但安全網的設計本身需要定期 review（例如 error 的定義是否涵蓋了所有異常模式）。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a>：per-team cost visibility 是治理的起點。</li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理</a>：cardinality budget 跟 label review 的操作流程。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：log tiering 跟 adaptive sampling 是 pipeline 的 routing 跟 processing 層配置。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測帳單每季成長 &gt; 20%，但服務的 request volume 成長遠小於此 — cardinality 或 log volume 可能在失控成長</li>
<li>管理層問「監控花多少錢、誰在用」但沒人能回答</li>
<li>曾經做過「全域降 retention」或「全域降 sampling」的成本優化，但幾個月後成本回升</li>
<li>Platform team 花大量時間處理「Prometheus OOM」或「Elasticsearch disk full」而非改善觀測品質</li>
<li>團隊的 debug log level 在 production 預設開著，理由是「不知道什麼時候需要」</li>
</ul>
]]></content:encoded></item><item><title>3.C14 Yelp：Schematizer 自建 Schema Registry</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/</guid><description>&lt;p>這個案例的核心責任是說明 schema 治理是 data pipeline 的核心責任、不是 add-on。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Yelp data pipeline 一天數十億訊息、跨數百個 service、數千 schema、用自建 Schematizer 強制所有 message 走 Avro schema、訊息只帶 schema ID。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Schematizer 不只是 schema store、還做 schema evolution compatibility 與 topic 自動分配（不相容 schema 強制新 topic）。揭露 producer / consumer schema 治理要拉到平台層、靠工具強制、不靠人約定。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：Schema Registry / Schema evolution。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineeringblog.yelp.com/2016/08/more-than-just-a-schema-store.html">Yelp Schematizer: More than just a schema store&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 schema 治理是 data pipeline 的核心責任、不是 add-on。</p>
<h2 id="觀察">觀察</h2>
<p>Yelp data pipeline 一天數十億訊息、跨數百個 service、數千 schema、用自建 Schematizer 強制所有 message 走 Avro schema、訊息只帶 schema ID。</p>
<h2 id="判讀">判讀</h2>
<p>Schematizer 不只是 schema store、還做 schema evolution compatibility 與 topic 自動分配（不相容 schema 強制新 topic）。揭露 producer / consumer schema 治理要拉到平台層、靠工具強制、不靠人約定。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：Schema Registry / Schema evolution。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineeringblog.yelp.com/2016/08/more-than-just-a-schema-store.html">Yelp Schematizer: More than just a schema store</a></li>
</ul>
]]></content:encoded></item><item><title>9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/</guid><description>&lt;p>這個案例的核心責任是說明「受監管產業」的容量規劃跟「網路服務」的本質差異。銀行交易系統的容量目標不只是「能撐多少」、還要同時滿足合規（資料駐留、稽核、加密、可恢復性）、跟一般工程性能優化的取捨完全不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Standard Chartered 在 Aurora 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/search/">AWS search results&lt;/a> 與相關 case study）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>遷移前&lt;/th>
 &lt;th>遷移後 (Aurora)&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>交易吞吐 (TPS)&lt;/td>
 &lt;td>（未公開、基線值）&lt;/td>
 &lt;td>4000 TPS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>吞吐倍數&lt;/td>
 &lt;td>1x baseline&lt;/td>
 &lt;td>10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>受監管市場&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>7 個（首批遷移）&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>服務組合：Amazon Aurora（PostgreSQL 或 MySQL 相容）、加密 at rest / in transit、多 AZ 部署、跨地區複製（受監管市場各自獨立）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>受監管銀行案例揭露三個合規驅動容量規劃的重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>資料駐留限制 = 容量規劃的單位是「per 市場」&lt;/strong>：7 個受監管市場代表 7 個獨立 cluster（資料不能跨境）、容量規劃變成「7 個獨立規劃 × 各自合規門檻」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的合規要求識別、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的地理分片。&lt;/li>
&lt;li>&lt;strong>「韌性 + 性能」並列、不是 trade-off&lt;/strong>：傳統工程文化常把可靠性跟性能視為對立、銀行業務要求兩者同時達標。Aurora 的多 AZ storage + replica 同時提供性能（讀分流）跟韌性（故障切換）、達成 &lt;em>韌性即性能&lt;/em> 的目標。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance&lt;/a> 的可靠性指標。&lt;/li>
&lt;li>&lt;strong>遷移本身的合規驗證 = 容量規劃延伸&lt;/strong>：受監管系統遷移不只是技術測試、還要過合規審查（中央銀行 / 金融監管機關）、每個市場各自審。這個審查 lead time（數月）必須算進遷移時程。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a> 的合規驅動 migration。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「10x throughput」是 &lt;em>vs 舊系統&lt;/em>、不是 &lt;em>vs 競爭對手&lt;/em>。受監管銀行的舊系統通常是 1990s-2000s 的 mainframe 或自建 OLTP、性能本來就低。讀案例時要對標的是「自家改善幅度」、不是「絕對性能」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>資料駐留是容量規劃的硬限制、不是優化選項&lt;/strong>：受監管市場必須各自獨立 cluster、不能用「全球單一 cluster」優化。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale&lt;/a> 的合規限制。&lt;/li>
&lt;li>&lt;strong>多 AZ + 跨地區複製是合規基線、不是優化&lt;/strong>：銀行業務 RPO / RTO 通常由監管要求（不能丟資料、必須 X 小時內恢復）、不是業務 SLA 選項。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">06.7 DR rollback rehearsal&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移時程要算合規 lead time&lt;/strong>：每個受監管市場的審查可能 3-12 個月、合計遷移時程是「市場數 × 平均審查月份」、不是「技術遷移月份」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Azure SQL Hyperscale + Azure regions、GCP Cloud SQL / Spanner + regional configurations、各家雲端的受監管雲端方案（AWS GovCloud、Azure Government、GCP Assured Workloads）都是對等候選。差異是各家對特定監管框架（PCI-DSS、ISO27001、各國金融法規）的認證覆蓋。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「受監管產業」的容量規劃跟「網路服務」的本質差異。銀行交易系統的容量目標不只是「能撐多少」、還要同時滿足合規（資料駐留、稽核、加密、可恢復性）、跟一般工程性能優化的取捨完全不同。</p>
<h2 id="觀察">觀察</h2>
<p>Standard Chartered 在 Aurora 的關鍵敘述（引自 <a href="https://aws.amazon.com/search/">AWS search results</a> 與相關 case study）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>遷移前</th>
          <th>遷移後 (Aurora)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>交易吞吐 (TPS)</td>
          <td>（未公開、基線值）</td>
          <td>4000 TPS</td>
      </tr>
      <tr>
          <td>吞吐倍數</td>
          <td>1x baseline</td>
          <td>10x</td>
      </tr>
      <tr>
          <td>受監管市場</td>
          <td>-</td>
          <td>7 個（首批遷移）</td>
      </tr>
      <tr>
          <td>成本下降</td>
          <td>-</td>
          <td>「顯著」（未公開具體數字）</td>
      </tr>
      <tr>
          <td>主要驅動</td>
          <td>韌性 + 性能</td>
          <td>-</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon Aurora（PostgreSQL 或 MySQL 相容）、加密 at rest / in transit、多 AZ 部署、跨地區複製（受監管市場各自獨立）。</p>
<h2 id="判讀">判讀</h2>
<p>受監管銀行案例揭露三個合規驅動容量規劃的重點。</p>
<ol>
<li><strong>資料駐留限制 = 容量規劃的單位是「per 市場」</strong>：7 個受監管市場代表 7 個獨立 cluster（資料不能跨境）、容量規劃變成「7 個獨立規劃 × 各自合規門檻」。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的合規要求識別、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的地理分片。</li>
<li><strong>「韌性 + 性能」並列、不是 trade-off</strong>：傳統工程文化常把可靠性跟性能視為對立、銀行業務要求兩者同時達標。Aurora 的多 AZ storage + replica 同時提供性能（讀分流）跟韌性（故障切換）、達成 <em>韌性即性能</em> 的目標。對應 <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance</a> 的可靠性指標。</li>
<li><strong>遷移本身的合規驗證 = 容量規劃延伸</strong>：受監管系統遷移不只是技術測試、還要過合規審查（中央銀行 / 金融監管機關）、每個市場各自審。這個審查 lead time（數月）必須算進遷移時程。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的合規驅動 migration。</li>
</ol>
<p>需要警惕：「10x throughput」是 <em>vs 舊系統</em>、不是 <em>vs 競爭對手</em>。受監管銀行的舊系統通常是 1990s-2000s 的 mainframe 或自建 OLTP、性能本來就低。讀案例時要對標的是「自家改善幅度」、不是「絕對性能」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>資料駐留是容量規劃的硬限制、不是優化選項</strong>：受監管市場必須各自獨立 cluster、不能用「全球單一 cluster」優化。對應 <a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale</a> 的合規限制。</li>
<li><strong>多 AZ + 跨地區複製是合規基線、不是優化</strong>：銀行業務 RPO / RTO 通常由監管要求（不能丟資料、必須 X 小時內恢復）、不是業務 SLA 選項。對應 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">06.7 DR rollback rehearsal</a>。</li>
<li><strong>遷移時程要算合規 lead time</strong>：每個受監管市場的審查可能 3-12 個月、合計遷移時程是「市場數 × 平均審查月份」、不是「技術遷移月份」。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
</ol>
<p>跨平台等效：Azure SQL Hyperscale + Azure regions、GCP Cloud SQL / Spanner + regional configurations、各家雲端的受監管雲端方案（AWS GovCloud、Azure Government、GCP Assured Workloads）都是對等候選。差異是各家對特定監管框架（PCI-DSS、ISO27001、各國金融法規）的認證覆蓋。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃受監管產業 OLTP → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想做合規驅動的容量規劃 → <a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想理解韌性跟性能的同步達成 → <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance</a></li>
<li>對照其他金融交易案例 → <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora</a> / <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a></li>
<li>想拆解跨 AZ failover RTO 量級與合規 anti-recommendation → <a href="/blog/backend/01-database/vendors/aurora/cross-az-failover-rto/" data-link-title="Aurora Cross-AZ Failover：RTO 量測、endpoint routing 與 application reconnect 契約" data-link-desc="Aurora cross-AZ failover lifecycle（detection / promotion / DNS update）、&lt; 30 秒 RTO、application DNS cache 跟 connection pool 對齊、Standard Chartered 受監管場景為什麼用獨立 cluster 而非 Global Database failover">Aurora 跨 AZ failover RTO</a></li>
<li>想評估全球資料常駐與多 region 部署 → <a href="/blog/backend/01-database/vendors/aurora/global-database-multi-region/" data-link-title="Aurora Global Database：跨 region async replication、&lt; 1 秒 lag 與合規 anti-recommendation" data-link-desc="Aurora Global Database 跨 region storage-level async replication、&lt; 1 秒 typical lag、planned vs unplanned failover RTO 數量級對比、Standard Chartered 合規禁止跨境複製為什麼讓 Global Database 變反指標">Aurora global database 多 region</a></li>
<li>想對照 distributed SQL（CockroachDB / Aurora DSQL / Spanner）的合規場景 → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/rds/aurora/customers/">Amazon Aurora Customer Stories</a></li>
<li><a href="https://aws.amazon.com/blogs/industries/amazon-aurora-for-core-banking-systems/">Amazon Aurora for Core Banking Systems</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-dsql-for-global-scale-financial-transactions/">Amazon Aurora DSQL for global-scale financial transactions</a></li>
</ul>
]]></content:encoded></item><item><title>Azure AD / Entra ID</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/</guid><description>&lt;p>Azure AD（現 Entra ID）是 Microsoft 生態的 identity 控制面、其失效會讓所有依賴 SSO 的服務無法登入、是 identity-as-cascading-point 的代表。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Identity 控制面 single point of cascading：SSO 失效擴散到所有下游&lt;/li>
&lt;li>配置變更 staged rollout 的限制：identity 服務難以 region-staged&lt;/li>
&lt;li>Token cache 緩衝：客戶端 token 有效期決定 outage 感受時間&lt;/li>
&lt;li>跨產品依賴：M365 / Teams / GitHub Enterprise 等的隱性依賴&lt;/li>
&lt;/ul>
&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>2020&lt;/td>
 &lt;td>多次全球登入失效&lt;/td>
 &lt;td>Identity cascading、staged rollout 限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2021&lt;/td>
 &lt;td>DNS / token service&lt;/td>
 &lt;td>Identity 服務的 sub-component 風險&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Azure AD 這個案例在講的是 identity 控制面一旦退化，許多看似獨立的服務都會一起受影響。讀者先看懂 Entra ID、Service Health 與 M365 health console 的分工，再把身份驗證視為跨服務的基礎路由。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 identity control plane 出現異常時，恢復順序往往比單一服務本身更重要。先讓監控與通訊路徑回穩，再處理驗證與登入流量，才能避免修復過程再度放大故障。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否把身份驗證失效與單一應用失效分開判讀&lt;/li>
&lt;li>能否從 Service Health 找到影響範圍與恢復節奏&lt;/li>
&lt;li>能否把 PIR 與 health dashboard 當成同一條對外路由&lt;/li>
&lt;li>能否辨識哪些障礙來自 identity，哪些來自下游服務&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Azure AD 是 Microsoft 365、GitHub Enterprise 與其他 SaaS 服務的基礎路由，這讓它和 AWS S3、GCP 一樣都屬於「控制面失效會放大」的案例。它最適合拿來和 Microsoft 365 一起讀，因為兩者分別描述了 identity 層與協作層的相依關係。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2020 年多次全球登入失效是 identity cascading 的典型樣本。&lt;/li>
&lt;li>2021 年 DNS / token service 問題則顯示 sub-component 也能放大成平台級風險。&lt;/li>
&lt;li>Azure Service Health 與 M365 health console 是對外路由的關鍵。&lt;/li>
&lt;li>token cache 會決定 outage 在使用者端維持多久。&lt;/li>
&lt;li>identity 是所有 SSO 服務的基礎路由。&lt;/li>
&lt;li>staged rollout 在 identity 服務上特別難做，因為影響面太大。&lt;/li>
&lt;li>token service 與 DNS 故障會把身份驗證整體拉下來。&lt;/li>
&lt;li>service health 變成客戶理解影響範圍的第一手資訊。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">AZ1&lt;/a>&lt;/td>
 &lt;td>身分控制面中斷&lt;/td>
 &lt;td>盤點跨產品身份依賴與分級回復順序&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-sla-performance">Service Level Agreement performance for Microsoft Entra ID&lt;/a>：Entra ID 的 SLA / incident history 入口。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/azure/service-health/service-health-overview">What is Azure Service Health?&lt;/a>：Azure Service Health 與 status / advisories 的官方說明。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/microsoft-365/enterprise/view-service-health?view=o365-worldwide">How to check Microsoft 365 service health&lt;/a>：M365/Entra 相關 health console 的用法。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/mt-mt/azure/reliability">Azure reliability documentation&lt;/a>：Azure 可靠性文件總入口。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Azure AD（現 Entra ID）是 Microsoft 生態的 identity 控制面、其失效會讓所有依賴 SSO 的服務無法登入、是 identity-as-cascading-point 的代表。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Identity 控制面 single point of cascading：SSO 失效擴散到所有下游</li>
<li>配置變更 staged rollout 的限制：identity 服務難以 region-staged</li>
<li>Token cache 緩衝：客戶端 token 有效期決定 outage 感受時間</li>
<li>跨產品依賴：M365 / Teams / GitHub Enterprise 等的隱性依賴</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2020</td>
          <td>多次全球登入失效</td>
          <td>Identity cascading、staged rollout 限制</td>
      </tr>
      <tr>
          <td>2021</td>
          <td>DNS / token service</td>
          <td>Identity 服務的 sub-component 風險</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Azure AD 這個案例在講的是 identity 控制面一旦退化，許多看似獨立的服務都會一起受影響。讀者先看懂 Entra ID、Service Health 與 M365 health console 的分工，再把身份驗證視為跨服務的基礎路由。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 identity control plane 出現異常時，恢復順序往往比單一服務本身更重要。先讓監控與通訊路徑回穩，再處理驗證與登入流量，才能避免修復過程再度放大故障。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否把身份驗證失效與單一應用失效分開判讀</li>
<li>能否從 Service Health 找到影響範圍與恢復節奏</li>
<li>能否把 PIR 與 health dashboard 當成同一條對外路由</li>
<li>能否辨識哪些障礙來自 identity，哪些來自下游服務</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Azure AD 是 Microsoft 365、GitHub Enterprise 與其他 SaaS 服務的基礎路由，這讓它和 AWS S3、GCP 一樣都屬於「控制面失效會放大」的案例。它最適合拿來和 Microsoft 365 一起讀，因為兩者分別描述了 identity 層與協作層的相依關係。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2020 年多次全球登入失效是 identity cascading 的典型樣本。</li>
<li>2021 年 DNS / token service 問題則顯示 sub-component 也能放大成平台級風險。</li>
<li>Azure Service Health 與 M365 health console 是對外路由的關鍵。</li>
<li>token cache 會決定 outage 在使用者端維持多久。</li>
<li>identity 是所有 SSO 服務的基礎路由。</li>
<li>staged rollout 在 identity 服務上特別難做，因為影響面太大。</li>
<li>token service 與 DNS 故障會把身份驗證整體拉下來。</li>
<li>service health 變成客戶理解影響範圍的第一手資訊。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">AZ1</a></td>
          <td>身分控制面中斷</td>
          <td>盤點跨產品身份依賴與分級回復順序</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-sla-performance">Service Level Agreement performance for Microsoft Entra ID</a>：Entra ID 的 SLA / incident history 入口。</li>
<li><a href="https://learn.microsoft.com/en-us/azure/service-health/service-health-overview">What is Azure Service Health?</a>：Azure Service Health 與 status / advisories 的官方說明。</li>
<li><a href="https://learn.microsoft.com/microsoft-365/enterprise/view-service-health?view=o365-worldwide">How to check Microsoft 365 service health</a>：M365/Entra 相關 health console 的用法。</li>
<li><a href="https://learn.microsoft.com/mt-mt/azure/reliability">Azure reliability documentation</a>：Azure 可靠性文件總入口。</li>
</ul>
]]></content:encoded></item><item><title>3.C15 Airbnb：Spark Streaming Kafka reader rebalance</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/</guid><description>&lt;p>這個案例的核心責任是說明 stream processor 與 Kafka partition 數的緊耦合是 production scaling 瓶頸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb logging pipeline 跨多個 topic、event size 從幾百 bytes 到幾百 KB、QPS 跨數個量級差異、Spark 一個 partition 對一個 task 造成 data skew、catch-up 一個 4 小時 lag 要再花 4 小時。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 balanced Spark Kafka reader、把 parallelism 從 partition 數解耦、按 event volume × size 重新分派 work。揭露 partition 數不該等同 consumer parallelism、要看 event 形狀。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：Consumer 設計 / consumer lag / rebalance / partition + consumer group。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/scaling-spark-streaming-for-logging-event-ingestion-4a03141d135d">Scaling Spark Streaming for Logging Event Ingestion&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 stream processor 與 Kafka partition 數的緊耦合是 production scaling 瓶頸。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb logging pipeline 跨多個 topic、event size 從幾百 bytes 到幾百 KB、QPS 跨數個量級差異、Spark 一個 partition 對一個 task 造成 data skew、catch-up 一個 4 小時 lag 要再花 4 小時。</p>
<h2 id="判讀">判讀</h2>
<p>自建 balanced Spark Kafka reader、把 parallelism 從 partition 數解耦、按 event volume × size 重新分派 work。揭露 partition 數不該等同 consumer parallelism、要看 event 形狀。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：Consumer 設計 / consumer lag / rebalance / partition + consumer group。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/scaling-spark-streaming-for-logging-event-ingestion-4a03141d135d">Scaling Spark Streaming for Logging Event Ingestion</a></li>
</ul>
]]></content:encoded></item><item><title>9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/</guid><description>&lt;p>這個案例的核心責任是說明「售票搶購型 flash-sale」的負載形狀 — 跟現有所有案例都不同的極端形狀。售票開賣在精確時間點（例如 12:00:00）瞬間湧入數十萬使用者、5 分鐘內賣完、之後流量歸零。這種「t=0 起跳、t=300 結束」的負載沒有「峰值預測」可言、只有「瞬間吸收」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>拓元 Tixcraft 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study&lt;/a> 與 &lt;a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">AWS re:Invent 2015 簡報&lt;/a>）：&lt;/p>
&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>100,000+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>訂單峰值&lt;/td>
 &lt;td>每分鐘 70,000+ 訂單、單秒最高 2,500+ 訂單&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3 分鐘內售出&lt;/td>
 &lt;td>30,000+ 張票&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DynamoDB IOPS 範圍&lt;/td>
 &lt;td>20 → 135,000（2015/8/29 峰值）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資源擴張幅度&lt;/td>
 &lt;td>30 分鐘內從 6 台擴到 800 台（130x）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署時間&lt;/td>
 &lt;td>1,600 工時 → 20 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>壓測規模&lt;/td>
 &lt;td>10,000 台 t2.micro、$130 / 小時&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務總成本&lt;/td>
 &lt;td>&amp;lt; 2 台 MacBook Pro（約 $4,200）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>vs 傳統基礎設施成本&lt;/td>
 &lt;td>0.26%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成立年份&lt;/td>
 &lt;td>2013 年底（雲原生）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合（依用戶提供的架構圖）：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>入口&lt;/strong>：Amazon Route 53（DNS）+ CloudFront + S3（靜態資源 static.tixcraft.com）&lt;/li>
&lt;li>&lt;strong>UI 層&lt;/strong>：Elastic Load Balancing → EC2 跨 3 個 Availability Zone（Tixcraft UI）&lt;/li>
&lt;li>&lt;strong>API 層&lt;/strong>：ELB → EC2 跨 3 個 AZ（API）+ ElastiCache 加速 session&lt;/li>
&lt;li>&lt;strong>資料層&lt;/strong>：DynamoDB 作為主要寫入目標（接 UI 寫入跟 API 寫入）&lt;/li>
&lt;li>&lt;strong>付款層&lt;/strong>：獨立的 EC2 Payment、連到 traditional server（合作金流、跑於企業 data center）&lt;/li>
&lt;li>&lt;strong>同步層&lt;/strong>：S3 Sync + EC2 Bridge 跟 corporate data center 的 backend 雙向同步&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>拓元案例最值得讀的、是它揭露三個 flash-sale 工程設計的非直覺事實。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>DynamoDB 作為寫入緩衝、不是 OLTP&lt;/strong>：搶票時的「訂單」先丟進 DynamoDB、傳統 server 用自己能承受的速度消費、即時生效在此架構下不是目標。架構上 DynamoDB 扮演 &lt;em>durable queue&lt;/em> 的角色、不是傳統 OLTP DB。這層解耦讓「前端可以擴 130 倍、後端不用同步擴」、避免後端被前端拖垮。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 outbox / async delivery 概念、跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 transaction boundary 分離。&lt;/li>
&lt;li>&lt;strong>DynamoDB IOPS 從 20 衝到 135,000 = partition 設計能撐&lt;/strong>：這個 6,750 倍的彈性不是 DynamoDB 魔法、是 &lt;em>partition key 設計均勻&lt;/em> 的結果。partition key 不均、IOPS 上限是「最熱 partition 上限」、不是「總和」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的同一判讀重點、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;li>&lt;strong>30 分鐘擴 130 倍 = 雲原生架構的存在證明&lt;/strong>：6 台 → 800 台不是手動操作、是 Auto Scaling Group + AMI prebuild + load balancer warmup 的組合。傳統 IDC 做不到。這層彈性是「30 秒內」flash-sale 的前置條件。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 autoscaling 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「售票搶購型 flash-sale」的負載形狀 — 跟現有所有案例都不同的極端形狀。售票開賣在精確時間點（例如 12:00:00）瞬間湧入數十萬使用者、5 分鐘內賣完、之後流量歸零。這種「t=0 起跳、t=300 結束」的負載沒有「峰值預測」可言、只有「瞬間吸收」。</p>
<h2 id="觀察">觀察</h2>
<p>拓元 Tixcraft 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study</a> 與 <a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">AWS re:Invent 2015 簡報</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同時選位用戶</td>
          <td>100,000+</td>
      </tr>
      <tr>
          <td>訂單峰值</td>
          <td>每分鐘 70,000+ 訂單、單秒最高 2,500+ 訂單</td>
      </tr>
      <tr>
          <td>3 分鐘內售出</td>
          <td>30,000+ 張票</td>
      </tr>
      <tr>
          <td>DynamoDB IOPS 範圍</td>
          <td>20 → 135,000（2015/8/29 峰值）</td>
      </tr>
      <tr>
          <td>資源擴張幅度</td>
          <td>30 分鐘內從 6 台擴到 800 台（130x）</td>
      </tr>
      <tr>
          <td>部署時間</td>
          <td>1,600 工時 → 20 分鐘</td>
      </tr>
      <tr>
          <td>壓測規模</td>
          <td>10,000 台 t2.micro、$130 / 小時</td>
      </tr>
      <tr>
          <td>任務總成本</td>
          <td>&lt; 2 台 MacBook Pro（約 $4,200）</td>
      </tr>
      <tr>
          <td>vs 傳統基礎設施成本</td>
          <td>0.26%</td>
      </tr>
      <tr>
          <td>成立年份</td>
          <td>2013 年底（雲原生）</td>
      </tr>
  </tbody>
</table>
<p>服務組合（依用戶提供的架構圖）：</p>
<ul>
<li><strong>入口</strong>：Amazon Route 53（DNS）+ CloudFront + S3（靜態資源 static.tixcraft.com）</li>
<li><strong>UI 層</strong>：Elastic Load Balancing → EC2 跨 3 個 Availability Zone（Tixcraft UI）</li>
<li><strong>API 層</strong>：ELB → EC2 跨 3 個 AZ（API）+ ElastiCache 加速 session</li>
<li><strong>資料層</strong>：DynamoDB 作為主要寫入目標（接 UI 寫入跟 API 寫入）</li>
<li><strong>付款層</strong>：獨立的 EC2 Payment、連到 traditional server（合作金流、跑於企業 data center）</li>
<li><strong>同步層</strong>：S3 Sync + EC2 Bridge 跟 corporate data center 的 backend 雙向同步</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>拓元案例最值得讀的、是它揭露三個 flash-sale 工程設計的非直覺事實。</p>
<ol>
<li><strong>DynamoDB 作為寫入緩衝、不是 OLTP</strong>：搶票時的「訂單」先丟進 DynamoDB、傳統 server 用自己能承受的速度消費、即時生效在此架構下不是目標。架構上 DynamoDB 扮演 <em>durable queue</em> 的角色、不是傳統 OLTP DB。這層解耦讓「前端可以擴 130 倍、後端不用同步擴」、避免後端被前端拖垮。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 outbox / async delivery 概念、跟 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 transaction boundary 分離。</li>
<li><strong>DynamoDB IOPS 從 20 衝到 135,000 = partition 設計能撐</strong>：這個 6,750 倍的彈性不是 DynamoDB 魔法、是 <em>partition key 設計均勻</em> 的結果。partition key 不均、IOPS 上限是「最熱 partition 上限」、不是「總和」。對應 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 的同一判讀重點、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
<li><strong>30 分鐘擴 130 倍 = 雲原生架構的存在證明</strong>：6 台 → 800 台不是手動操作、是 Auto Scaling Group + AMI prebuild + load balancer warmup 的組合。傳統 IDC 做不到。這層彈性是「30 秒內」flash-sale 的前置條件。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 autoscaling 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>「限流到底怎麼做」這個工程社群關心的問題、架構圖上看不到明確元件。可能是「DynamoDB 寫入排隊 = 隱性限流」、也可能是 ELB / WAF / 應用層限流。沒有公開資訊不要過度推測。</li>
<li>2015 年的數字、用的還是 t2.micro 跟舊版 DynamoDB throughput model。現在等效實作可能會用 DynamoDB on-demand、AWS WAF、CloudFront WAF rules、或 SeatGeek-style Virtual Waiting Room（見 <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16</a>）。</li>
<li>「30,000 張 / 3 分鐘」是 <em>票房成績</em>、不是 <em>系統極限</em>。系統能撐遠不止這個量、只是票本身賣完了。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>flash-sale 的核心架構模式：寫入緩衝 + 慢速消費</strong>：前端把訂單塞進可彈性擴容的儲存（DynamoDB / Redis Stream / Kafka）、後端按自己能力消費。這個模式讓「短時間吸收洪峰」跟「實際處理」解耦。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 與 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a>。</li>
<li><strong>partition key 設計是 flash-sale 的命脈</strong>：搶票場景天然容易 hot partition（同一場演唱會 = 同一 event_id）、必須用 composite key（event_id + user_id_hash）或 write sharding（event_id + random_suffix）分散。對應 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a>。</li>
<li><strong>flash-sale 必須事先 ELB / Auto Scaling 預熱</strong>：開賣前 30-60 分鐘 pre-warm ELB、預先啟動最低額度的 EC2、避免 t=0 時冷啟動。對應 AWS 官方 <a href="https://aws.amazon.com/blogs/mt/top-considerations-for-flash-sale-events/">Flash Sale 工程指引</a>。</li>
<li><strong>付款層獨立、不被搶票流量影響</strong>：拓元把 Payment EC2 拉出來、直連傳統金流 server。讓「選位 + 下單」的高頻流量不會塞爆「付款」的低頻流量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的關鍵路徑切分。</li>
<li><strong>限流（rate limiting）通常是隱性的、不一定看得到 component</strong>：DynamoDB 寫入排隊本身就是隱性限流；也可以加 WAF rate-based rule、ELB request throttling、或前置 Virtual Waiting Room 做明確限流（見 <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16</a>）。</li>
</ol>
<p>跨平台等效：GCP Cloud Spanner / Bigtable + Cloud Pub/Sub 作 buffer + GKE autoscaling；Azure Cosmos DB + Service Bus + AKS；自建 PostgreSQL + Kafka + Kubernetes 都可以實作對等架構。差異是 vendor 整合度跟擴容速度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 flash-sale 緩衝架構 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做 partition key 設計 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> + <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想做明確限流 / 排隊機制 → <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek Virtual Waiting Room</a></li>
<li>想預熱 ELB / Auto Scaling → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>對照其他售票市場 → <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a>（印度市場、年售 2 億張）</li>
<li>想理解 flash-sale 場景的 partition key 反模式 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估 on-demand vs provisioned 在 flash-sale 的搭配 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study (AWS)</a></li>
<li><a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">tixCraft on AWS re:Invent 2015 Recap (SlideShare)</a></li>
<li><a href="https://www.youtube.com/watch?v=Bi-1xjXvKgs">tixCraft: Handling Millions of Ticketing Requests with AWS (YouTube)</a></li>
<li><a href="https://aws.amazon.com/blogs/mt/top-considerations-for-flash-sale-events/">Top considerations for Flash sale events (AWS Cloud Operations Blog)</a></li>
<li><a href="https://aws.amazon.com/blogs/database/handle-traffic-spikes-with-amazon-dynamodb-provisioned-capacity/">Handle traffic spikes with Amazon DynamoDB provisioned capacity</a></li>
</ul>
]]></content:encoded></item><item><title>3.C16 Robinhood：Faust Python stream processing</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/</guid><description>&lt;p>這個案例的核心責任是說明語言生態與 stream framework 的選型張力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Robinhood 每天處理 billions of events / TB 資料、用於 risk signal、order quality、market data、fraud detection；team 多為 Python、不想用 JVM 生態。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>把 Kafka Streams 的 stateful streaming 模式（topology、tables、windowing）移植到 Python library 形式、不需要 Yarn / Mesos resource manager。揭露 stream processing framework 選型常被語言生態主導、不是技術 feature。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：跨語言 client / Streams framework / stream processing on Kafka。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/robinhood-engineering/faust-stream-processing-for-python-a66d3a51212d">Faust: Stream Processing for Python&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明語言生態與 stream framework 的選型張力。</p>
<h2 id="觀察">觀察</h2>
<p>Robinhood 每天處理 billions of events / TB 資料、用於 risk signal、order quality、market data、fraud detection；team 多為 Python、不想用 JVM 生態。</p>
<h2 id="判讀">判讀</h2>
<p>把 Kafka Streams 的 stateful streaming 模式（topology、tables、windowing）移植到 Python library 形式、不需要 Yarn / Mesos resource manager。揭露 stream processing framework 選型常被語言生態主導、不是技術 feature。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：跨語言 client / Streams framework / stream processing on Kafka。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/robinhood-engineering/faust-stream-processing-for-python-a66d3a51212d">Faust: Stream Processing for Python</a></li>
</ul>
]]></content:encoded></item><item><title>9.C16 SeatGeek：DynamoDB + Lambda 打造的虛擬等候室</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/</guid><description>&lt;p>這個案例的核心責任是說明「flash-sale 場景下、限流如何明確設計」。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「DynamoDB 隱性緩衝」是姊妹案 — Tixcraft 用 DynamoDB 作為寫入緩衝吸收洪峰、SeatGeek 走更上游一層、在用戶到達系統前就明確排隊。兩種架構並存於票務業界、適合不同業務場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>SeatGeek Virtual Waiting Room 架構（引自 &lt;a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">AWS Architecture Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>元件&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Protected Zone table&lt;/td>
 &lt;td>紀錄受保護資源的 metadata（哪個 event 受 waiting room 保護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Counters table&lt;/td>
 &lt;td>紀錄「每分鐘發出多少 access token」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>User Connection table&lt;/td>
 &lt;td>紀錄訪客 token 與 WebSocket connection ID&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Queue table&lt;/td>
 &lt;td>把訪客 token 對映到 access token（排隊序號）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bouncer Lambda&lt;/td>
 &lt;td>配發與失效 access token 的「守門員」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API Gateway&lt;/td>
 &lt;td>接受外部請求、轉發 Bouncer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>業務動機：取代「第三方 waiting room 服務」、原因是缺乏客製化（VIP 規則、優先級）跟 metrics 可見度。&lt;/p>
&lt;p>關鍵機制：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Token = 庫存單位&lt;/strong>：access token 總數 = 可售票數量。沒拿到 token 的用戶被導到 waiting room 頁面、看到排隊位置與預估等待時間。&lt;/li>
&lt;li>&lt;strong>FIFO 或 priority queue&lt;/strong>：可以按進入順序、也可以對 VIP 客戶優先發 token。&lt;/li>
&lt;li>&lt;strong>Token 失效機制&lt;/strong>：用戶完成購票 / 主動退出時、token 釋放回 pool、給下一位等候用戶。&lt;/li>
&lt;/ol>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SeatGeek 案例揭露三個明確限流設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>隱性緩衝 vs 明確排隊是兩種架構取捨&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">Tixcraft 模式&lt;/a>「全部塞進 DynamoDB」、用戶以為下單成功、實際處理排隊。SeatGeek 模式「明確告訴你排隊位置」、用戶看得到等待時間。前者犧牲透明度換流量吸收、後者犧牲流量吸收換體驗。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.10 Production-Side 驗證&lt;/a> 的用戶體驗 vs 系統行為取捨。&lt;/li>
&lt;li>&lt;strong>WebSocket connection 是 stateful 容量單位&lt;/strong>：100 萬個 active waiting room 用戶 = 100 萬個 WebSocket connection、每個 connection 都吃記憶體跟 file descriptor。Lambda 沒辦法保持 WebSocket、需要 API Gateway WebSocket API 或 AppSync 配合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 stateful service 容量規劃。&lt;/li>
&lt;li>&lt;strong>限流粒度 = 業務粒度&lt;/strong>：「每分鐘發 N 個 token」這個參數直接決定「每分鐘成交 N 張票」。N 太小、賣不完；N 太大、後端撐不住。N 不是技術參數、是業務 × 後端容量的協商結果。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 把容量規劃跟業務 KPI 對接。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「flash-sale 場景下、限流如何明確設計」。跟 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> 的「DynamoDB 隱性緩衝」是姊妹案 — Tixcraft 用 DynamoDB 作為寫入緩衝吸收洪峰、SeatGeek 走更上游一層、在用戶到達系統前就明確排隊。兩種架構並存於票務業界、適合不同業務場景。</p>
<h2 id="觀察">觀察</h2>
<p>SeatGeek Virtual Waiting Room 架構（引自 <a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">AWS Architecture Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Protected Zone table</td>
          <td>紀錄受保護資源的 metadata（哪個 event 受 waiting room 保護）</td>
      </tr>
      <tr>
          <td>Counters table</td>
          <td>紀錄「每分鐘發出多少 access token」</td>
      </tr>
      <tr>
          <td>User Connection table</td>
          <td>紀錄訪客 token 與 WebSocket connection ID</td>
      </tr>
      <tr>
          <td>Queue table</td>
          <td>把訪客 token 對映到 access token（排隊序號）</td>
      </tr>
      <tr>
          <td>Bouncer Lambda</td>
          <td>配發與失效 access token 的「守門員」</td>
      </tr>
      <tr>
          <td>API Gateway</td>
          <td>接受外部請求、轉發 Bouncer</td>
      </tr>
  </tbody>
</table>
<p>業務動機：取代「第三方 waiting room 服務」、原因是缺乏客製化（VIP 規則、優先級）跟 metrics 可見度。</p>
<p>關鍵機制：</p>
<ol>
<li><strong>Token = 庫存單位</strong>：access token 總數 = 可售票數量。沒拿到 token 的用戶被導到 waiting room 頁面、看到排隊位置與預估等待時間。</li>
<li><strong>FIFO 或 priority queue</strong>：可以按進入順序、也可以對 VIP 客戶優先發 token。</li>
<li><strong>Token 失效機制</strong>：用戶完成購票 / 主動退出時、token 釋放回 pool、給下一位等候用戶。</li>
</ol>
<h2 id="判讀">判讀</h2>
<p>SeatGeek 案例揭露三個明確限流設計重點。</p>
<ol>
<li><strong>隱性緩衝 vs 明確排隊是兩種架構取捨</strong>：<a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">Tixcraft 模式</a>「全部塞進 DynamoDB」、用戶以為下單成功、實際處理排隊。SeatGeek 模式「明確告訴你排隊位置」、用戶看得到等待時間。前者犧牲透明度換流量吸收、後者犧牲流量吸收換體驗。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.10 Production-Side 驗證</a> 的用戶體驗 vs 系統行為取捨。</li>
<li><strong>WebSocket connection 是 stateful 容量單位</strong>：100 萬個 active waiting room 用戶 = 100 萬個 WebSocket connection、每個 connection 都吃記憶體跟 file descriptor。Lambda 沒辦法保持 WebSocket、需要 API Gateway WebSocket API 或 AppSync 配合。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 stateful service 容量規劃。</li>
<li><strong>限流粒度 = 業務粒度</strong>：「每分鐘發 N 個 token」這個參數直接決定「每分鐘成交 N 張票」。N 太小、賣不完；N 太大、後端撐不住。N 不是技術參數、是業務 × 後端容量的協商結果。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 把容量規劃跟業務 KPI 對接。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>AWS Architecture Blog 沒提具體流量數字（concurrent users、queue depth、throughput）。讀者無法直接套用到自家容量規劃、必須自己壓測。</li>
<li>DynamoDB 4 張表的設計 <em>看似簡單</em>、實際上每張表的 partition key / sort key 設計都要仔細想。複製這個架構不等於拿到 SeatGeek 的吞吐能力。</li>
<li>「token expiration」機制如果設計不好（例如用戶關閉瀏覽器、token 沒回收）、會導致「排隊很長但實際空著」、影響轉換率。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>明確 vs 隱性限流的選擇</strong>：高價值門票（演唱會、限量周邊）適合明確排隊（用戶願意等）；高頻低價值商品（FCFS 折扣）適合隱性緩衝（讓用戶快速完成）。</li>
<li><strong>Virtual Waiting Room 是 stateful service、要規劃連線容量</strong>：不是 stateless Lambda 一招到底、需要 WebSocket gateway + DynamoDB state store。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的混合架構。</li>
<li><strong>token 過期策略要寫進設計初稿</strong>：用戶離開、付款超時、瀏覽器當掉 — 三種狀況的 token 回收邏輯都不一樣、要明確設計。</li>
<li><strong>可觀測性是「自建 waiting room」勝過「第三方」的關鍵</strong>：SeatGeek 換掉第三方就是要 metrics 可見、知道每分鐘 token issue rate、queue depth distribution、token expiration rate、conversion funnel。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>。</li>
</ol>
<p>跨平台等效：GCP Cloud Functions + Firestore + Pub/Sub；Azure Functions + Cosmos DB + SignalR；自建 Redis（INCR / TTL）+ WebSocket gateway（Soketi / Socket.IO + Redis adapter）都可以實作對等架構。AWS 還推出官方 <a href="https://aws.amazon.com/solutions/implementations/virtual-waiting-room-on-aws/">Virtual Waiting Room on AWS</a> Solutions、是 SeatGeek 模式的可重用版本。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計明確排隊限流 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>對照隱性緩衝模式 → <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a></li>
<li>想做 conversion funnel 可觀測性 → <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>想了解 stateful service 容量規劃 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek</a></li>
<li><a href="https://aws.amazon.com/solutions/implementations/virtual-waiting-room-on-aws/">Virtual Waiting Room on AWS (Solutions)</a></li>
<li><a href="https://aws.amazon.com/blogs/apn/how-to-manage-peak-traffic-on-aws-using-queue-its-virtual-waiting-room/">How to manage peak traffic on AWS using Queue-it&rsquo;s virtual waiting room</a></li>
</ul>
]]></content:encoded></item><item><title>3.C17 Walmart：Messaging Proxy Service 解 rebalance storm</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/</guid><description>&lt;p>這個案例的核心責任是說明 partition-consumer 1:1 模型在大規模 K8s 環境的擴張極限。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Walmart 每天 trillions of message、25K+ Kafka consumer 跑在 WCNP Kubernetes 多雲環境；最大痛點是 pod scaling / deploy / heartbeat fail 觸發 consumer rebalance、lag spike。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 Messaging Proxy Service（MPS、Kafka Connect sink connector）、把 consumer 從 partition-bound 解耦成 stateless REST service、可獨立 auto-scale、不用增 partition；內建 DLQ 處理 poison pill。揭露「consumer 該跟 partition 數綁定」這個假設在 K8s 規模化下不再成立。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：rebalance storm / consumer lag / multi-tenant 配額。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/walmartglobaltech/reliably-processing-trillions-of-kafka-messages-per-day-23494f553ef9">Reliably Processing Trillions of Kafka Messages Per Day&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 partition-consumer 1:1 模型在大規模 K8s 環境的擴張極限。</p>
<h2 id="觀察">觀察</h2>
<p>Walmart 每天 trillions of message、25K+ Kafka consumer 跑在 WCNP Kubernetes 多雲環境；最大痛點是 pod scaling / deploy / heartbeat fail 觸發 consumer rebalance、lag spike。</p>
<h2 id="判讀">判讀</h2>
<p>自建 Messaging Proxy Service（MPS、Kafka Connect sink connector）、把 consumer 從 partition-bound 解耦成 stateless REST service、可獨立 auto-scale、不用增 partition；內建 DLQ 處理 poison pill。揭露「consumer 該跟 partition 數綁定」這個假設在 K8s 規模化下不再成立。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：rebalance storm / consumer lag / multi-tenant 配額。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/walmartglobaltech/reliably-processing-trillions-of-kafka-messages-per-day-23494f553ef9">Reliably Processing Trillions of Kafka Messages Per Day</a></li>
</ul>
]]></content:encoded></item><item><title>9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/</guid><description>&lt;p>這個案例的核心責任是說明「規模化 ticketing 平台」的長期工程議題 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「單一搶票事件」不同、BookMyShow 是 &lt;em>每天都有上百個 flash-sale 事件&lt;/em> 的平台、年售 2 億張票、跨 5 個國家。容量問題從「單一峰值」變成「峰值的常態化」、加上「資料層怎麼跟得上業務變化」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>BookMyShow 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">BookMyShow AWS Migration Blog&lt;/a>）：&lt;/p>
&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>2 億張 / 年（pre-COVID baseline）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>印度 + 斯里蘭卡 + 新加坡 + 印尼 + 中東&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移時程&lt;/td>
 &lt;td>4 個月完成&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>舊系統年數&lt;/td>
 &lt;td>15 年自建 analytics solution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>儲存成本下降&lt;/td>
 &lt;td>90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分析成本下降&lt;/td>
 &lt;td>80%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料整合&lt;/td>
 &lt;td>從 80 TB 多份副本 → 單一 source of truth&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>資料架構：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Data Lake&lt;/strong>：Amazon S3 統一儲存&lt;/li>
&lt;li>&lt;strong>Ingestion&lt;/strong>：Kafka consumers、AWS Glue ETL、AWS IoT Core（MQTT）&lt;/li>
&lt;li>&lt;strong>Processing&lt;/strong>：Amazon EMR（streaming permanent cluster + batch transient cluster）&lt;/li>
&lt;li>&lt;strong>Data Warehouse&lt;/strong>：Amazon Redshift + materialized views&lt;/li>
&lt;li>&lt;strong>Analytics&lt;/strong>：Amazon Athena（ad-hoc）+ Amazon QuickSight（dashboard）&lt;/li>
&lt;li>&lt;strong>ML&lt;/strong>：Amazon SageMaker（內容熱度、活動熱度、搜尋趨勢模型）&lt;/li>
&lt;li>&lt;strong>Orchestration&lt;/strong>：Amazon MWAA + AWS Step Functions&lt;/li>
&lt;/ul>
&lt;p>關鍵業務支撐：「sudden spikes with new movies or events launched」靠 serverless（S3、Glue、Athena、Step Functions、Lambda）自動擴容、無需人工介入。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>BookMyShow 案例揭露三個規模化 ticketing 平台的長期工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單一搶票 → 常態多事件 = 架構從「為峰值設計」變「為流量分佈設計」&lt;/strong>：每天上百場電影 + 數十場演唱會 + 各種活動同時開票、每場都是 mini flash-sale。容量問題不再是「為一場演唱會準備」、而是「為每天上百個峰值同時準備」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 從單一 workload 變成 workload portfolio。&lt;/li>
&lt;li>&lt;strong>資料層比交易層更難擴&lt;/strong>：8 TB → 80 TB 過程中、舊 analytics 系統用 15 年才走到極限。交易層擴容靠 stateless EC2 + auto-scaling 相對容易、資料層 schema migration、ETL 重寫、報表回對都是長 lead time 工作。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema migration 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的 cost attribution。&lt;/li>
&lt;li>&lt;strong>跨國市場 = 多重合規約束&lt;/strong>：印度、新加坡、印尼、中東各自有資料駐留 / 加密 / 報稅規則。S3 + EMR + Redshift 的「資料分區」不只是性能議題、也是合規議題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered&lt;/a> 的合規容量規劃。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「規模化 ticketing 平台」的長期工程議題 — 跟 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> 的「單一搶票事件」不同、BookMyShow 是 <em>每天都有上百個 flash-sale 事件</em> 的平台、年售 2 億張票、跨 5 個國家。容量問題從「單一峰值」變成「峰值的常態化」、加上「資料層怎麼跟得上業務變化」。</p>
<h2 id="觀察">觀察</h2>
<p>BookMyShow 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">BookMyShow AWS Migration Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年售票量</td>
          <td>2 億張 / 年（pre-COVID baseline）</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>印度 + 斯里蘭卡 + 新加坡 + 印尼 + 中東</td>
      </tr>
      <tr>
          <td>遷移時程</td>
          <td>4 個月完成</td>
      </tr>
      <tr>
          <td>舊系統年數</td>
          <td>15 年自建 analytics solution</td>
      </tr>
      <tr>
          <td>儲存成本下降</td>
          <td>90%</td>
      </tr>
      <tr>
          <td>分析成本下降</td>
          <td>80%</td>
      </tr>
      <tr>
          <td>資料整合</td>
          <td>從 80 TB 多份副本 → 單一 source of truth</td>
      </tr>
  </tbody>
</table>
<p>資料架構：</p>
<ul>
<li><strong>Data Lake</strong>：Amazon S3 統一儲存</li>
<li><strong>Ingestion</strong>：Kafka consumers、AWS Glue ETL、AWS IoT Core（MQTT）</li>
<li><strong>Processing</strong>：Amazon EMR（streaming permanent cluster + batch transient cluster）</li>
<li><strong>Data Warehouse</strong>：Amazon Redshift + materialized views</li>
<li><strong>Analytics</strong>：Amazon Athena（ad-hoc）+ Amazon QuickSight（dashboard）</li>
<li><strong>ML</strong>：Amazon SageMaker（內容熱度、活動熱度、搜尋趨勢模型）</li>
<li><strong>Orchestration</strong>：Amazon MWAA + AWS Step Functions</li>
</ul>
<p>關鍵業務支撐：「sudden spikes with new movies or events launched」靠 serverless（S3、Glue、Athena、Step Functions、Lambda）自動擴容、無需人工介入。</p>
<h2 id="判讀">判讀</h2>
<p>BookMyShow 案例揭露三個規模化 ticketing 平台的長期工程重點。</p>
<ol>
<li><strong>單一搶票 → 常態多事件 = 架構從「為峰值設計」變「為流量分佈設計」</strong>：每天上百場電影 + 數十場演唱會 + 各種活動同時開票、每場都是 mini flash-sale。容量問題不再是「為一場演唱會準備」、而是「為每天上百個峰值同時準備」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 從單一 workload 變成 workload portfolio。</li>
<li><strong>資料層比交易層更難擴</strong>：8 TB → 80 TB 過程中、舊 analytics 系統用 15 年才走到極限。交易層擴容靠 stateless EC2 + auto-scaling 相對容易、資料層 schema migration、ETL 重寫、報表回對都是長 lead time 工作。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema migration 與 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 cost attribution。</li>
<li><strong>跨國市場 = 多重合規約束</strong>：印度、新加坡、印尼、中東各自有資料駐留 / 加密 / 報稅規則。S3 + EMR + Redshift 的「資料分區」不只是性能議題、也是合規議題。對應 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> 的合規容量規劃。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>「年售 2 億張」是 <em>年度總和</em>、不是 <em>峰值</em>。實際單秒峰值（板球比賽決賽開票、寶萊塢新片首映）案例本身沒揭露。</li>
<li>案例聚焦在 <em>資料分析層</em> 的遷移、不是 <em>交易層</em> 的 flash-sale 設計。讀者若想學「單場 flash-sale 怎麼撐」、應該回 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> 或 <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek</a>。</li>
<li>「80% 成本下降」是 <em>vs 15 年舊系統</em>、不是 <em>vs 競爭對手</em>。舊系統的儲存效率、運維成本本來就低、改善幅度部分來自「現代化紅利」、不只是 AWS 服務本身。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>大規模 ticketing 平台要分「交易層」跟「資料層」兩條容量規劃</strong>：交易層為單一 event flash-sale 設計（<a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15</a> / <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16</a> 模式）；資料層為「上千場活動的長期分析」設計（BookMyShow 模式）。兩者用不同服務、不同 SLO。</li>
<li><strong>跨國平台先解決資料駐留、再規劃跨國 analytics</strong>：印度資料不能搬到新加坡分析、合規必須各國資料本地處理、再彙整 metadata。對應 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a>。</li>
<li><strong>serverless data stack 是 ticketing 平台的長期方向</strong>：S3 + Glue + Athena + Step Functions 的成本曲線比 EMR cluster 平穩、沒事件時近乎 0、有事件時自動擴。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移時程 4 個月 = 計畫密度極高</strong>：15 年資產 4 個月遷完不是常態、需要先把 <em>資料模型</em> canonical 化、再 batch 平行遷。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的 schema 對映先行。</li>
</ol>
<p>跨平台等效：GCP BigQuery + Dataflow + Cloud Storage + Pub/Sub 是對等 stack；Azure Synapse + Data Lake + Event Hubs；自建 Delta Lake + Spark + Kafka 都可以實作對等架構。差異是 vendor 整合度跟 serverless 透明度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃多事件 ticketing 平台 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想看單一 flash-sale 設計 → <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> + <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek</a></li>
<li>想做跨國合規容量規劃 → <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想做大規模 migration → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> + <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify migration</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">How BookMyShow saved 80% in costs by migrating to an AWS modern data architecture</a></li>
<li><a href="https://aws.amazon.com/architecture/analytics-big-data/">AWS Modern Data Architecture</a></li>
</ul>
]]></content:encoded></item><item><title>3.C18 Wix：Greyhound TLLSR 解 consumer 卡住</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/</guid><description>&lt;p>這個案例的核心責任是說明大規模 multi-tenant Kafka 的營運可視性需求遠超原生 metric。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wix 2000+ microservice、每天 66 billion Kafka 訊息、用自建 Greyhound（JVM library + polyglot sidecar）抽象 Kafka；troubleshooting 痛點是「卡住的 consumer 看不到原因、只能寫 DB 修復腳本」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>TLLSR 框架（Trace / Lookup / Longest-running / Skip-replay / Redistribute）解 single-partition lag、單筆 poison pill、handler 卡住等情境；consumer lag alert &amp;gt; 30 分鐘觸發。揭露原生 lag metric 無法定位「卡在哪」、需要 message-level trace + 操作介面。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：consumer lag / observability / multi-tenant / poison message。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/wix-engineering/troubleshooting-kafka-for-2000-microservices-at-wix-986ee382fd1e">Troubleshooting Kafka for 2000 Microservices at Wix&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模 multi-tenant Kafka 的營運可視性需求遠超原生 metric。</p>
<h2 id="觀察">觀察</h2>
<p>Wix 2000+ microservice、每天 66 billion Kafka 訊息、用自建 Greyhound（JVM library + polyglot sidecar）抽象 Kafka；troubleshooting 痛點是「卡住的 consumer 看不到原因、只能寫 DB 修復腳本」。</p>
<h2 id="判讀">判讀</h2>
<p>TLLSR 框架（Trace / Lookup / Longest-running / Skip-replay / Redistribute）解 single-partition lag、單筆 poison pill、handler 卡住等情境；consumer lag alert &gt; 30 分鐘觸發。揭露原生 lag metric 無法定位「卡在哪」、需要 message-level trace + 操作介面。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：consumer lag / observability / multi-tenant / poison message。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/wix-engineering/troubleshooting-kafka-for-2000-microservices-at-wix-986ee382fd1e">Troubleshooting Kafka for 2000 Microservices at Wix</a></li>
</ul>
]]></content:encoded></item><item><title>9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/</guid><description>&lt;p>這個案例的核心責任是說明「SaaS 類 surge」跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokemon GO&lt;/a> 的「product surge」差異。Zoom 的 30 倍成長不是「產品爆紅」、是「外部事件（COVID）逼全世界改變工作模式」、突發是 &lt;em>結構性&lt;/em> 的、不是回歸均值的暫時現象。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Zoom 在 2020 年 COVID 期間的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&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>1000 萬 → 3 億（2020 年 3 月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成長倍數&lt;/td>
 &lt;td>30x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主資料層&lt;/td>
 &lt;td>Amazon DynamoDB（會議 metadata）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴容描述&lt;/td>
 &lt;td>「nearly infinitely with no performance issues」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「On the backend, they were able to manage this surge with Amazon DynamoDB for Zoom Meetings.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Zoom surge 揭露三個 SaaS 突發成長的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>SaaS surge 是結構性、不是暫時性&lt;/strong>：Pokemon GO 上線爆紅後流量會隨熱度消退、Zoom COVID 成長是「永久 baseline 上移」。容量規劃不能假設「過幾個月會回來」、必須假設「3 億 DAU 是新常態」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的長期 baseline 重新校準。&lt;/li>
&lt;li>&lt;strong>DynamoDB 「無限擴容」對 SaaS 元資料層特別適用&lt;/strong>：Zoom 會議 metadata（room ID、participant list、permission state）是典型 KV 工作負載、partition key（meeting_id）天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 同樣的 partition 均勻優勢。&lt;/li>
&lt;li>&lt;strong>媒體串流不在 DynamoDB&lt;/strong>：Zoom 的影音流量是 P2P + edge servers、不經 DynamoDB。DynamoDB 只承擔「control plane」、不承擔「data plane」。這個分離是擴 30 倍的前提 — 控制面跟資料面解耦、控制面用 managed 服務、資料面用專屬基礎設施。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的關鍵路徑切分。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「nearly infinitely」是行銷敘述、不是工程承諾。實務上 Zoom 在 COVID 初期確實遇到 outage 與性能問題、後續才穩定。讀案例時要看 &lt;em>最終狀態&lt;/em> 跟 &lt;em>過程中的 incident&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>控制面跟資料面分離&lt;/strong>：高頻 metadata 操作放 managed KV（DynamoDB / Cosmos DB / Firestore）、大資料量串流放專屬基礎設施（CDN / WebRTC / 自管 servers）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>。&lt;/li>
&lt;li>&lt;strong>surge 後重新校準 SLO baseline&lt;/strong>：30x 成長之後、SLO 的「正常範圍」要更新、否則 monitoring 會誤報。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 SLO 演進。&lt;/li>
&lt;li>&lt;strong>長期 surge 觸發架構重新評估&lt;/strong>：DynamoDB 是「擴大量」的好選擇、但成本也跟著放大。當 baseline 從 1000 萬永久升到 3 億、原本的 on-demand 模式可能變得貴、要考慮 provisioned + auto-scaling 組合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Google Meet 也用 Spanner / Firestore、Microsoft Teams 用 Cosmos DB — 三家視訊會議都靠 managed KV 撐 metadata、是同一個架構模式的不同 vendor 實作。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「SaaS 類 surge」跟 <a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokemon GO</a> 的「product surge」差異。Zoom 的 30 倍成長不是「產品爆紅」、是「外部事件（COVID）逼全世界改變工作模式」、突發是 <em>結構性</em> 的、不是回歸均值的暫時現象。</p>
<h2 id="觀察">觀察</h2>
<p>Zoom 在 2020 年 COVID 期間的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>日活參與者</td>
          <td>1000 萬 → 3 億（2020 年 3 月）</td>
      </tr>
      <tr>
          <td>成長倍數</td>
          <td>30x</td>
      </tr>
      <tr>
          <td>主資料層</td>
          <td>Amazon DynamoDB（會議 metadata）</td>
      </tr>
      <tr>
          <td>擴容描述</td>
          <td>「nearly infinitely with no performance issues」</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「On the backend, they were able to manage this surge with Amazon DynamoDB for Zoom Meetings.」</p>
<h2 id="判讀">判讀</h2>
<p>Zoom surge 揭露三個 SaaS 突發成長的工程重點。</p>
<ol>
<li><strong>SaaS surge 是結構性、不是暫時性</strong>：Pokemon GO 上線爆紅後流量會隨熱度消退、Zoom COVID 成長是「永久 baseline 上移」。容量規劃不能假設「過幾個月會回來」、必須假設「3 億 DAU 是新常態」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的長期 baseline 重新校準。</li>
<li><strong>DynamoDB 「無限擴容」對 SaaS 元資料層特別適用</strong>：Zoom 會議 metadata（room ID、participant list、permission state）是典型 KV 工作負載、partition key（meeting_id）天然均勻、不會 hot partition。對應 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 同樣的 partition 均勻優勢。</li>
<li><strong>媒體串流不在 DynamoDB</strong>：Zoom 的影音流量是 P2P + edge servers、不經 DynamoDB。DynamoDB 只承擔「control plane」、不承擔「data plane」。這個分離是擴 30 倍的前提 — 控制面跟資料面解耦、控制面用 managed 服務、資料面用專屬基礎設施。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的關鍵路徑切分。</li>
</ol>
<p>需要警惕：「nearly infinitely」是行銷敘述、不是工程承諾。實務上 Zoom 在 COVID 初期確實遇到 outage 與性能問題、後續才穩定。讀案例時要看 <em>最終狀態</em> 跟 <em>過程中的 incident</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>控制面跟資料面分離</strong>：高頻 metadata 操作放 managed KV（DynamoDB / Cosmos DB / Firestore）、大資料量串流放專屬基礎設施（CDN / WebRTC / 自管 servers）。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>surge 後重新校準 SLO baseline</strong>：30x 成長之後、SLO 的「正常範圍」要更新、否則 monitoring 會誤報。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 SLO 演進。</li>
<li><strong>長期 surge 觸發架構重新評估</strong>：DynamoDB 是「擴大量」的好選擇、但成本也跟著放大。當 baseline 從 1000 萬永久升到 3 億、原本的 on-demand 模式可能變得貴、要考慮 provisioned + auto-scaling 組合。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>跨平台等效：Google Meet 也用 Spanner / Firestore、Microsoft Teams 用 Cosmos DB — 三家視訊會議都靠 managed KV 撐 metadata、是同一個架構模式的不同 vendor 實作。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照 product surge → <a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokemon GO</a></li>
<li>想理解 control plane vs data plane → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想規劃 surge 後的 SLO → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>想評估 surge 下的 on-demand vs provisioned 切換 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
<li>想避免 surge 觸發 hot partition → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/innovators/zoom/">Zoom Video Communications on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C19 Wix：Multi-cluster Kafka zero-downtime 遷移</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/</guid><description>&lt;p>這個案例的核心責任是說明 single mega-cluster 的 metadata scaling ceiling 與分群策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wix cluster metadata 從 2019 年 5K topic / 45K partition 漲到 20K topic / 200K partition、每日 record 從 450M 漲到 2.5B、controller startup 與 broker stability 受 metadata 量壓垮。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不用 MirrorMaker、自建 Replicator service + Migration Orchestrator、用 Kafka topic 當控制平面協調 consumer 切換 + offset mapping；按 SLA 切多 cluster。揭露「topic / partition 數量」是 broker 級別的物理上限、不能無限擴張。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / topic 生命週期 / 分層叢集策略。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3 LinkedIn TopicGC&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/wix-engineering/migrating-to-a-multi-cluster-managed-kafka-with-0-downtime-b936655f888e">Migrating to a Multi-Cluster Managed Kafka with 0 Downtime&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 single mega-cluster 的 metadata scaling ceiling 與分群策略。</p>
<h2 id="觀察">觀察</h2>
<p>Wix cluster metadata 從 2019 年 5K topic / 45K partition 漲到 20K topic / 200K partition、每日 record 從 450M 漲到 2.5B、controller startup 與 broker stability 受 metadata 量壓垮。</p>
<h2 id="判讀">判讀</h2>
<p>不用 MirrorMaker、自建 Replicator service + Migration Orchestrator、用 Kafka topic 當控制平面協調 consumer 切換 + offset mapping；按 SLA 切多 cluster。揭露「topic / partition 數量」是 broker 級別的物理上限、不能無限擴張。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / topic 生命週期 / 分層叢集策略。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3 LinkedIn TopicGC</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/wix-engineering/migrating-to-a-multi-cluster-managed-kafka-with-0-downtime-b936655f888e">Migrating to a Multi-Cluster Managed Kafka with 0 Downtime</a></li>
</ul>
]]></content:encoded></item><item><title>9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB + EKS 上的遊戲後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/</guid><description>&lt;p>這個案例的核心責任是說明「遊戲後端 KV」跟「廣告 KV」「電商 KV」的業務語意差異。遊戲後端的 KV 工作負載特性是：玩家狀態（角色、裝備、戰績）必須次秒讀寫、跨 region 同步、防作弊 — 這層需求跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的「廣告量測」或 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 的「AR 玩家位置」都不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Capcom 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/capcom/">Capcom Case Study&lt;/a> 與 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>遊戲 IP&lt;/td>
 &lt;td>Resident Evil、Street Fighter、Monster Hunter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>後端請求量&lt;/td>
 &lt;td>billions of requests&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>響應時間&lt;/td>
 &lt;td>single-digit millisecond&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>營運成本下降&lt;/td>
 &lt;td>30%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB + Amazon EKS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程資源再配置&lt;/td>
 &lt;td>從 DB 運維轉到遊戲品質與開發週期&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「Capcom uses Amazon DynamoDB to meet this demand with single-digit millisecond response times」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Capcom 案例揭露三個遊戲後端 KV 的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>遊戲後端 KV = 跨遊戲共用基礎設施&lt;/strong>：Resident Evil / Street Fighter / Monster Hunter 是不同類型遊戲（單機+多人 / 對戰 / 合作打怪）、卻共用 &lt;em>同一套後端 KV&lt;/em>。這個共用降低了單一遊戲的維運成本、也讓新遊戲上線時不用重做基礎設施。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 multi-tenant platform。&lt;/li>
&lt;li>&lt;strong>single-digit ms response time = 玩家體感「即時」的底線&lt;/strong>：戰鬥動作、技能釋放、玩家對戰都要次秒級反應、超過 10ms 就「卡」。這個延遲門檻反推 Capcom 必須用 sub-region cache（ElastiCache / 本地 game server）+ DynamoDB DAX、不能單靠 DynamoDB。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 的延遲反推。&lt;/li>
&lt;li>&lt;strong>「工程資源從 DB 運維轉到遊戲品質」是 managed 服務的真實價值&lt;/strong>：Capcom 不是 IT 公司、是遊戲公司。把 DBA 時間從「Postgres patching、replication 設定、backup 排程」釋放到「遊戲機制設計、玩家行為分析」、才是 30% 成本下降的本質。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「billions of requests」沒指明時間單位（每秒、每天、每月）。讀案例時要找具體單位、不要直接套用到自家。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>遊戲後端 KV 用 DynamoDB / Cosmos DB / Bigtable&lt;/strong>：partition key 用 player_id 天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema 設計。&lt;/li>
&lt;li>&lt;strong>EKS 跑 game server、不直接連 DynamoDB&lt;/strong>：game server 處理遊戲邏輯（戰鬥、配對、防作弊）、DynamoDB 處理持久狀態。中間用 DAX 或 ElastiCache 減少 DynamoDB 呼叫。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>。&lt;/li>
&lt;li>&lt;strong>多 IP / 多遊戲共用平台是降本核心&lt;/strong>：每個新遊戲不重做基礎設施、共用同一套 DynamoDB + EKS。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games&lt;/a> 的「single-tenant per game」對照 — 不同 IP 公司有不同取捨。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Bigtable + GKE + Memorystore、Azure Cosmos DB + AKS + Cache for Redis 都可實作對等架構。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「遊戲後端 KV」跟「廣告 KV」「電商 KV」的業務語意差異。遊戲後端的 KV 工作負載特性是：玩家狀態（角色、裝備、戰績）必須次秒讀寫、跨 region 同步、防作弊 — 這層需求跟 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 的「廣告量測」或 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 的「AR 玩家位置」都不同。</p>
<h2 id="觀察">觀察</h2>
<p>Capcom 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/solutions/case-studies/capcom/">Capcom Case Study</a> 與 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>遊戲 IP</td>
          <td>Resident Evil、Street Fighter、Monster Hunter</td>
      </tr>
      <tr>
          <td>後端請求量</td>
          <td>billions of requests</td>
      </tr>
      <tr>
          <td>響應時間</td>
          <td>single-digit millisecond</td>
      </tr>
      <tr>
          <td>營運成本下降</td>
          <td>30%</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB + Amazon EKS</td>
      </tr>
      <tr>
          <td>工程資源再配置</td>
          <td>從 DB 運維轉到遊戲品質與開發週期</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「Capcom uses Amazon DynamoDB to meet this demand with single-digit millisecond response times」。</p>
<h2 id="判讀">判讀</h2>
<p>Capcom 案例揭露三個遊戲後端 KV 的工程重點。</p>
<ol>
<li><strong>遊戲後端 KV = 跨遊戲共用基礎設施</strong>：Resident Evil / Street Fighter / Monster Hunter 是不同類型遊戲（單機+多人 / 對戰 / 合作打怪）、卻共用 <em>同一套後端 KV</em>。這個共用降低了單一遊戲的維運成本、也讓新遊戲上線時不用重做基礎設施。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 multi-tenant platform。</li>
<li><strong>single-digit ms response time = 玩家體感「即時」的底線</strong>：戰鬥動作、技能釋放、玩家對戰都要次秒級反應、超過 10ms 就「卡」。這個延遲門檻反推 Capcom 必須用 sub-region cache（ElastiCache / 本地 game server）+ DynamoDB DAX、不能單靠 DynamoDB。對應 <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> 的延遲反推。</li>
<li><strong>「工程資源從 DB 運維轉到遊戲品質」是 managed 服務的真實價值</strong>：Capcom 不是 IT 公司、是遊戲公司。把 DBA 時間從「Postgres patching、replication 設定、backup 排程」釋放到「遊戲機制設計、玩家行為分析」、才是 30% 成本下降的本質。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>需要警惕：「billions of requests」沒指明時間單位（每秒、每天、每月）。讀案例時要找具體單位、不要直接套用到自家。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>遊戲後端 KV 用 DynamoDB / Cosmos DB / Bigtable</strong>：partition key 用 player_id 天然均勻、不會 hot partition。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema 設計。</li>
<li><strong>EKS 跑 game server、不直接連 DynamoDB</strong>：game server 處理遊戲邏輯（戰鬥、配對、防作弊）、DynamoDB 處理持久狀態。中間用 DAX 或 ElastiCache 減少 DynamoDB 呼叫。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>多 IP / 多遊戲共用平台是降本核心</strong>：每個新遊戲不重做基礎設施、共用同一套 DynamoDB + EKS。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> 的「single-tenant per game」對照 — 不同 IP 公司有不同取捨。</li>
</ol>
<p>跨平台等效：GCP Bigtable + GKE + Memorystore、Azure Cosmos DB + AKS + Cache for Redis 都可實作對等架構。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他遊戲後端 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS</a>（cluster 隔離 vs 共用）</li>
<li>想設計遊戲 KV → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a></li>
<li>想理解 sub-ms latency 反推 → <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想規劃遊戲 KV access pattern 與 single-table design → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a></li>
<li>想評估遊戲流量的 on-demand vs provisioned → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/capcom/">CAPCOM Case Study</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/big-data/powering-gaming-applications-with-amazon-dynamodb/">Powering Gaming Applications with Amazon DynamoDB</a></li>
</ul>
]]></content:encoded></item><item><title>3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/</guid><description>&lt;p>Spotify 從 Kafka 遷出到 GCP Pub/Sub 的決策揭露了兩件事：broker 的可靠性保證是版本特性而非 Kafka 的不變量；以及「升級到新版」跟「換到另一個系統」之間的決策判準。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Spotify 的事件傳遞系統（Event Delivery）負責把使用者行為事件（播放、搜尋、推薦互動）從客戶端送到資料管線。系統跨 5 個 datacenter 運行 Kafka 0.7，production peak 700K events/sec、pressure test 達到 2M events/sec。事件資料是推薦系統、analytics 跟廣告計費的輸入，遺失事件直接影響商業決策的準確性。&lt;/p>
&lt;p>2016 年，Spotify 決定把 Event Delivery 從 Kafka 遷移到 GCP Pub/Sub，而非升級到當時已發布的 Kafka 0.8+。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="mirrormaker-的-best-effort-語意">MirrorMaker 的 best-effort 語意&lt;/h3>
&lt;p>Kafka 0.7 的跨 datacenter replication 工具 MirrorMaker 在 best-effort mode 下會丟失資料但向 producer 回報成功。對 Spotify 的場景，producer 端認為事件已送達，但跨 datacenter 的 mirror 實際上丟了一部分。丟失比例在正常情況下很低，但在 broker restart 或網路抖動時可以升高到影響 analytics 準確性的程度。&lt;/p>
&lt;p>這個問題的根源是 Kafka 0.7 的 producer 沒有 idempotent 保證，MirrorMaker 的 consumer offset commit 跟 producer ack 之間有 gap。&lt;/p>
&lt;h3 id="broker-restart-後-producer-無法自動恢復">Broker restart 後 producer 無法自動恢復&lt;/h3>
&lt;p>Kafka 0.7 的 producer 在 broker restart 後可能進入無法自動恢復的狀態 — 需要人工重啟 producer process。在 5 個 datacenter、數百個 producer instance 的規模下，每次 broker 維護操作都需要人工介入恢復 producer，運維成本跟 broker 數量成正比。&lt;/p>
&lt;h3 id="為什麼不升級到-kafka-08">為什麼不升級到 Kafka 0.8+&lt;/h3>
&lt;p>Kafka 0.8 引入了 replication、新的 consumer API 跟更可靠的 producer。但 Spotify 評估後認為升級的成本接近重新部署：&lt;/p>
&lt;ul>
&lt;li>Kafka 0.7 到 0.8 的 wire protocol 不相容，需要全量遷移而非滾動升級&lt;/li>
&lt;li>所有 producer / consumer 的 client library 都要更換&lt;/li>
&lt;li>Spotify 同時在向 GCP 遷移基礎設施，Kafka 的自管運維模式跟 GCP 的託管方向不一致&lt;/li>
&lt;/ul>
&lt;p>相比之下，GCP Pub/Sub 提供了託管的 exactly-once 語意、跨 region replication、零運維。遷移成本跟升級 Kafka 版本的成本相當，但遷移後的長期運維成本低得多。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>留在 Kafka（升級 0.8+）&lt;/th>
 &lt;th>遷到 GCP Pub/Sub&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>一次性遷移成本&lt;/td>
 &lt;td>中（全量遷移、不可滾動升級）&lt;/td>
 &lt;td>中（同樣需要改所有 client）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>長期運維成本&lt;/td>
 &lt;td>高（自管 broker × 5 DC）&lt;/td>
 &lt;td>低（託管、零 broker 維護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可靠性保證&lt;/td>
 &lt;td>0.8+ 有 replication、改善大&lt;/td>
 &lt;td>Pub/Sub 原生 exactly-once&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨 region replication&lt;/td>
 &lt;td>需要自建 MirrorMaker 2.0&lt;/td>
 &lt;td>原生支援&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生態鎖定&lt;/td>
 &lt;td>Kafka 生態成熟&lt;/td>
 &lt;td>GCP 鎖定、跨雲成本高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Spotify 的判斷是：在同時進行 GCP 遷移的背景下，維護自管 Kafka 的投資回報比不上切換到託管方案。這個判斷跟 Kafka 本身的能力無關 — Kafka 0.8+ 的可靠性已經解決了 0.7 的問題。決策的關鍵變數是「組織正在往哪走」，不只是「技術上哪個更好」。&lt;/p></description><content:encoded><![CDATA[<p>Spotify 從 Kafka 遷出到 GCP Pub/Sub 的決策揭露了兩件事：broker 的可靠性保證是版本特性而非 Kafka 的不變量；以及「升級到新版」跟「換到另一個系統」之間的決策判準。</p>
<h2 id="業務背景">業務背景</h2>
<p>Spotify 的事件傳遞系統（Event Delivery）負責把使用者行為事件（播放、搜尋、推薦互動）從客戶端送到資料管線。系統跨 5 個 datacenter 運行 Kafka 0.7，production peak 700K events/sec、pressure test 達到 2M events/sec。事件資料是推薦系統、analytics 跟廣告計費的輸入，遺失事件直接影響商業決策的準確性。</p>
<p>2016 年，Spotify 決定把 Event Delivery 從 Kafka 遷移到 GCP Pub/Sub，而非升級到當時已發布的 Kafka 0.8+。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="mirrormaker-的-best-effort-語意">MirrorMaker 的 best-effort 語意</h3>
<p>Kafka 0.7 的跨 datacenter replication 工具 MirrorMaker 在 best-effort mode 下會丟失資料但向 producer 回報成功。對 Spotify 的場景，producer 端認為事件已送達，但跨 datacenter 的 mirror 實際上丟了一部分。丟失比例在正常情況下很低，但在 broker restart 或網路抖動時可以升高到影響 analytics 準確性的程度。</p>
<p>這個問題的根源是 Kafka 0.7 的 producer 沒有 idempotent 保證，MirrorMaker 的 consumer offset commit 跟 producer ack 之間有 gap。</p>
<h3 id="broker-restart-後-producer-無法自動恢復">Broker restart 後 producer 無法自動恢復</h3>
<p>Kafka 0.7 的 producer 在 broker restart 後可能進入無法自動恢復的狀態 — 需要人工重啟 producer process。在 5 個 datacenter、數百個 producer instance 的規模下，每次 broker 維護操作都需要人工介入恢復 producer，運維成本跟 broker 數量成正比。</p>
<h3 id="為什麼不升級到-kafka-08">為什麼不升級到 Kafka 0.8+</h3>
<p>Kafka 0.8 引入了 replication、新的 consumer API 跟更可靠的 producer。但 Spotify 評估後認為升級的成本接近重新部署：</p>
<ul>
<li>Kafka 0.7 到 0.8 的 wire protocol 不相容，需要全量遷移而非滾動升級</li>
<li>所有 producer / consumer 的 client library 都要更換</li>
<li>Spotify 同時在向 GCP 遷移基礎設施，Kafka 的自管運維模式跟 GCP 的託管方向不一致</li>
</ul>
<p>相比之下，GCP Pub/Sub 提供了託管的 exactly-once 語意、跨 region replication、零運維。遷移成本跟升級 Kafka 版本的成本相當，但遷移後的長期運維成本低得多。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>留在 Kafka（升級 0.8+）</th>
          <th>遷到 GCP Pub/Sub</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一次性遷移成本</td>
          <td>中（全量遷移、不可滾動升級）</td>
          <td>中（同樣需要改所有 client）</td>
      </tr>
      <tr>
          <td>長期運維成本</td>
          <td>高（自管 broker × 5 DC）</td>
          <td>低（託管、零 broker 維護）</td>
      </tr>
      <tr>
          <td>可靠性保證</td>
          <td>0.8+ 有 replication、改善大</td>
          <td>Pub/Sub 原生 exactly-once</td>
      </tr>
      <tr>
          <td>跨 region replication</td>
          <td>需要自建 MirrorMaker 2.0</td>
          <td>原生支援</td>
      </tr>
      <tr>
          <td>生態鎖定</td>
          <td>Kafka 生態成熟</td>
          <td>GCP 鎖定、跨雲成本高</td>
      </tr>
  </tbody>
</table>
<p>Spotify 的判斷是：在同時進行 GCP 遷移的背景下，維護自管 Kafka 的投資回報比不上切換到託管方案。這個判斷跟 Kafka 本身的能力無關 — Kafka 0.8+ 的可靠性已經解決了 0.7 的問題。決策的關鍵變數是「組織正在往哪走」，不只是「技術上哪個更好」。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：cross-region replication 跟 MirrorMaker 的進階主題。Spotify 的案例是「早期版本限制」的歷史教訓，Kafka 3.x 的 KRaft + idempotent producer 已解決這些問題。</li>
<li><a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a>：託管 MQ 的定位跟適用場景。</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：exactly-once 語意的工程實踐。Spotify 案例揭露 exactly-once 在早期 Kafka 版本不成立。</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 版本跟可靠性保證的關係。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用舊版 Kafka（&lt; 2.0）且跨 region replication 的資料完整性無法驗證</li>
<li>Broker restart 後需要人工重啟 producer、運維成本跟 broker 數量成正比</li>
<li>組織正在做基礎設施遷移（on-prem → cloud），考慮是否同步切換 MQ</li>
<li>評估「升級現有系統 vs 遷移到新系統」的決策框架</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/03/spotifys-event-delivery-the-road-to-the-cloud-part-ii">Spotify&rsquo;s Event Delivery — The Road to the Cloud (Part II)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/</guid><description>&lt;p>這個案例的核心責任是提供「同樣業務需求、不同 DB 技術」的具體對照數字。Zomato 帳單系統從 TiDB 遷移到 DynamoDB、留下三個關鍵改善百分比、是 DB 選型決策的少見 &lt;em>可量化&lt;/em> 對照樣本。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Zomato 帳單系統遷移的關鍵數字（引自 &lt;a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">AWS Database Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>TiDB（遷移前）&lt;/th>
 &lt;th>DynamoDB（遷移後）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>微服務吞吐&lt;/td>
 &lt;td>2,000 RPM&lt;/td>
 &lt;td>8,000 RPM（4x）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲降幅&lt;/td>
 &lt;td>baseline&lt;/td>
 &lt;td>-90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本降幅&lt;/td>
 &lt;td>baseline&lt;/td>
 &lt;td>-50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>每日事件量&lt;/td>
 &lt;td>10M（共用）&lt;/td>
 &lt;td>10M&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>餐廳合作夥伴&lt;/td>
 &lt;td>350,000+&lt;/td>
 &lt;td>350,000+&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵動機：TiDB 必須為「突發流量峰值」提前 over-provision、付出常態成本；DynamoDB on-demand 模式「pay only for what we use」、避免 over-provisioning。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Zomato 遷移揭露三個 DB 選型決策的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>NewSQL vs NoSQL 的取捨不只是 schema&lt;/strong>：TiDB 提供 SQL 介面跟 ACID、DynamoDB 提供 KV 介面跟最終一致性。Zomato 選 DynamoDB 是判斷「帳單事件本身可以接受 eventually consistent」、用一致性換性能跟成本。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的一致性取捨。&lt;/li>
&lt;li>&lt;strong>TiDB 必須 over-provision 是分散式 SQL 的常態&lt;/strong>：分散式 SQL 為了支援跨節點交易、必須有預留容量、否則峰值會出現 leader election storm 或 follower lag。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a> 的「節點數即容量」是同類取捨、Spanner 也必須預先 scale 節點。&lt;/li>
&lt;li>&lt;strong>2K → 8K RPM 是 4 倍、但延遲降 90% 才是真關鍵&lt;/strong>：吞吐改善可能來自架構優化、延遲改善才是 DB 本質差。從 baseline → 10% 通常代表少了 1-2 個 hop（例如 cross-region replication、coordinator round-trip）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為&lt;/a> 的 Little&amp;rsquo;s Law。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「成本降 50%」是 &lt;em>當下流量下的對照&lt;/em>。如果未來流量繼續成長、DynamoDB 的 cost-per-request 成長率比 TiDB 自管 cluster 高 — 達到某規模後 TiDB 反而更便宜。讀遷移案例要看「在當下流量下划算」、不等於「永遠划算」。&lt;/li>
&lt;li>「90% 延遲降」可能只是 p50、p99 / p999 改善幅度通常較小。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>DB 遷移前先確認業務一致性需求&lt;/strong>：能接受 eventually consistent 的工作負載適合 KV / NoSQL；必須 strong consistency 的工作負載必須 SQL / NewSQL。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移評估要看「總成本曲線」、不是「當下 snapshot」&lt;/strong>：算未來 12-24 個月在預期流量下的成本對照、不是只算現在。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移過程要 dual-write + shadow read 驗證&lt;/strong>：避免新舊系統行為不一致導致業務問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a>。&lt;/li>
&lt;li>&lt;strong>on-demand vs provisioned 的選擇與業務流量形狀對應&lt;/strong>：突發流量適合 on-demand、可預測流量適合 provisioned。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的 on-demand 應用。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：MongoDB Atlas → DynamoDB、Cassandra → DynamoDB、PostgreSQL → Aurora、CockroachDB → Spanner 都是常見遷移路徑。每條路徑的取捨類似。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「同樣業務需求、不同 DB 技術」的具體對照數字。Zomato 帳單系統從 TiDB 遷移到 DynamoDB、留下三個關鍵改善百分比、是 DB 選型決策的少見 <em>可量化</em> 對照樣本。</p>
<h2 id="觀察">觀察</h2>
<p>Zomato 帳單系統遷移的關鍵數字（引自 <a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">AWS Database Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>TiDB（遷移前）</th>
          <th>DynamoDB（遷移後）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>微服務吞吐</td>
          <td>2,000 RPM</td>
          <td>8,000 RPM（4x）</td>
      </tr>
      <tr>
          <td>延遲降幅</td>
          <td>baseline</td>
          <td>-90%</td>
      </tr>
      <tr>
          <td>成本降幅</td>
          <td>baseline</td>
          <td>-50%</td>
      </tr>
      <tr>
          <td>每日事件量</td>
          <td>10M（共用）</td>
          <td>10M</td>
      </tr>
      <tr>
          <td>餐廳合作夥伴</td>
          <td>350,000+</td>
          <td>350,000+</td>
      </tr>
  </tbody>
</table>
<p>關鍵動機：TiDB 必須為「突發流量峰值」提前 over-provision、付出常態成本；DynamoDB on-demand 模式「pay only for what we use」、避免 over-provisioning。</p>
<h2 id="判讀">判讀</h2>
<p>Zomato 遷移揭露三個 DB 選型決策的判讀重點。</p>
<ol>
<li><strong>NewSQL vs NoSQL 的取捨不只是 schema</strong>：TiDB 提供 SQL 介面跟 ACID、DynamoDB 提供 KV 介面跟最終一致性。Zomato 選 DynamoDB 是判斷「帳單事件本身可以接受 eventually consistent」、用一致性換性能跟成本。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>TiDB 必須 over-provision 是分散式 SQL 的常態</strong>：分散式 SQL 為了支援跨節點交易、必須有預留容量、否則峰值會出現 leader election storm 或 follower lag。這跟 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的「節點數即容量」是同類取捨、Spanner 也必須預先 scale 節點。</li>
<li><strong>2K → 8K RPM 是 4 倍、但延遲降 90% 才是真關鍵</strong>：吞吐改善可能來自架構優化、延遲改善才是 DB 本質差。從 baseline → 10% 通常代表少了 1-2 個 hop（例如 cross-region replication、coordinator round-trip）。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> 的 Little&rsquo;s Law。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「成本降 50%」是 <em>當下流量下的對照</em>。如果未來流量繼續成長、DynamoDB 的 cost-per-request 成長率比 TiDB 自管 cluster 高 — 達到某規模後 TiDB 反而更便宜。讀遷移案例要看「在當下流量下划算」、不等於「永遠划算」。</li>
<li>「90% 延遲降」可能只是 p50、p99 / p999 改善幅度通常較小。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>DB 遷移前先確認業務一致性需求</strong>：能接受 eventually consistent 的工作負載適合 KV / NoSQL；必須 strong consistency 的工作負載必須 SQL / NewSQL。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a>。</li>
<li><strong>遷移評估要看「總成本曲線」、不是「當下 snapshot」</strong>：算未來 12-24 個月在預期流量下的成本對照、不是只算現在。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移過程要 dual-write + shadow read 驗證</strong>：避免新舊系統行為不一致導致業務問題。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a>。</li>
<li><strong>on-demand vs provisioned 的選擇與業務流量形狀對應</strong>：突發流量適合 on-demand、可預測流量適合 provisioned。對應 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> 的 on-demand 應用。</li>
</ol>
<p>跨平台等效：MongoDB Atlas → DynamoDB、Cassandra → DynamoDB、PostgreSQL → Aurora、CockroachDB → Spanner 都是常見遷移路徑。每條路徑的取捨類似。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做 DB 遷移評估 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解一致性取捨 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> + <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做總成本評估 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>對照其他 DB 遷移 → <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify Kafka→Pub/Sub</a></li>
<li>想拆 access pattern 對應的 DynamoDB schema → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a> + <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估搬遷後的 capacity mode → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">Unlocking performance, scalability, and cost-efficiency of Zomato&rsquo;s Billing Platform by switching from TiDB to DynamoDB</a></li>
<li><a href="https://aws.amazon.com/blogs/opensource/how-zomato-boosted-performance-25-and-cut-compute-cost-30-migrating-trino-and-druid-workloads-to-aws-graviton/">How Zomato Boosted Performance 25% and Cut Compute Cost 30% Migrating Trino and Druid Workloads to AWS Graviton</a></li>
</ul>
]]></content:encoded></item><item><title>3.C21 Goldman Sachs：MSK 遷移 with MirrorMaker 2</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/</guid><description>&lt;p>這個案例的核心責任是說明 MM2 在 production cutover 的真實 tuning 與 LB 整合 pitfall。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Global Investment Research 把 ~12 microservice / 30 instance 從 on-prem Kafka 遷到 MSK；用 MM2 同步 topic / ACL / consumer group / offset、選擇 atomic cutover、整體耗時 ~7 小時。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>把 MM2 預設的 prefixed topic 改成 identical name；遇到 flush timeout（5s → 30s）、request size、NLB idle timeout 350s vs client 540s 衝突。揭露 managed 服務遷移的細節風險集中在「LB / timeout / topic naming」這些 client 端配置、不在 broker 本身。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / managed broker 遷移 / ACL 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2 VMware → MSK&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/how-goldman-sachs-migrated-from-their-on-premises-apache-kafka-cluster-to-amazon-msk/">How Goldman Sachs Migrated from On-Premises Apache Kafka to Amazon MSK&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 MM2 在 production cutover 的真實 tuning 與 LB 整合 pitfall。</p>
<h2 id="觀察">觀察</h2>
<p>Global Investment Research 把 ~12 microservice / 30 instance 從 on-prem Kafka 遷到 MSK；用 MM2 同步 topic / ACL / consumer group / offset、選擇 atomic cutover、整體耗時 ~7 小時。</p>
<h2 id="判讀">判讀</h2>
<p>把 MM2 預設的 prefixed topic 改成 identical name；遇到 flush timeout（5s → 30s）、request size、NLB idle timeout 350s vs client 540s 衝突。揭露 managed 服務遷移的細節風險集中在「LB / timeout / topic naming」這些 client 端配置、不在 broker 本身。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / managed broker 遷移 / ACL 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2 VMware → MSK</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/how-goldman-sachs-migrated-from-their-on-premises-apache-kafka-cluster-to-amazon-msk/">How Goldman Sachs Migrated from On-Premises Apache Kafka to Amazon MSK</a></li>
</ul>
]]></content:encoded></item><item><title>9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/</guid><description>&lt;p>這個案例的核心責任是補強 Azure 案例庫深度。Cosmos DB 過往只有 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 一篇、ASOS 提供 &lt;em>傳統零售場景 + 全球分散 + 季節性峰值&lt;/em> 的對照、跟 Minecraft Earth 的 &lt;em>AR 遊戲 + 玩家位置&lt;/em> 完全不同業務語意。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>ASOS 在 Azure 的關鍵數字（引自 &lt;a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS Microsoft Customer Story&lt;/a>）：&lt;/p>
&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>1540 萬&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 24 小時請求量&lt;/td>
 &lt;td>1.67 億&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 請求峰值&lt;/td>
 &lt;td>3,500 req/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 訂單峰值&lt;/td>
 &lt;td>33 orders/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>平均響應時間&lt;/td>
 &lt;td>48 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品 SKU&lt;/td>
 &lt;td>85,000、每週新增 5,000 件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構轉變&lt;/td>
 &lt;td>2016 年遷移到 microservices&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Azure Cosmos DB + microservices&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵業務驅動：「ASOS chose Azure Cosmos DB because of its global distribution and ability to handle heavy seasonal bursts like Black Friday」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>ASOS 案例揭露三個全球零售 KV 容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Black Friday 24h 1.67 億 = 平均 1,930 req/sec、峰值 3,500 req/sec&lt;/strong>：峰值 / 平均 = 1.81 倍。這個比例顯示 Black Friday 「持續高峰」、不是「瞬間爆量」 — 24 小時內流量曲線相對平緩、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「5 分鐘賣完」是完全不同形狀。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的負載形狀識別。&lt;/li>
&lt;li>&lt;strong>48ms 平均響應 = 全球分散下 Cosmos DB 的代表性數字&lt;/strong>：英國時尚電商、客戶遍及全球、Cosmos DB 在每個地區複製、讀取在最近 region 完成。這個 48ms 包含網路、DB、應用層 — DB 本身可能只佔 5-10ms、其他是網路與應用層。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 分解。&lt;/li>
&lt;li>&lt;strong>85K SKU + 每週新增 5K = 高更新頻率 catalog&lt;/strong>：商品資料不只是讀、還有頻繁更新（價格、庫存、推薦排序）。這層 write throughput 對 Cosmos DB partition key 設計（通常用 category_id 或 brand_id）至關重要。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：這是 2016 年的數字、過去 10 年 ASOS 應該成長很多。但 1.67 億 req/24h 跟 33 orders/sec 對許多新興電商仍是天花板級數字、可作為「中大型零售」對標。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure 案例庫深度。Cosmos DB 過往只有 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 一篇、ASOS 提供 <em>傳統零售場景 + 全球分散 + 季節性峰值</em> 的對照、跟 Minecraft Earth 的 <em>AR 遊戲 + 玩家位置</em> 完全不同業務語意。</p>
<h2 id="觀察">觀察</h2>
<p>ASOS 在 Azure 的關鍵數字（引自 <a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS Microsoft Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>1540 萬</td>
      </tr>
      <tr>
          <td>Black Friday 24 小時請求量</td>
          <td>1.67 億</td>
      </tr>
      <tr>
          <td>Black Friday 請求峰值</td>
          <td>3,500 req/sec</td>
      </tr>
      <tr>
          <td>Black Friday 訂單峰值</td>
          <td>33 orders/sec</td>
      </tr>
      <tr>
          <td>平均響應時間</td>
          <td>48 ms</td>
      </tr>
      <tr>
          <td>商品 SKU</td>
          <td>85,000、每週新增 5,000 件</td>
      </tr>
      <tr>
          <td>架構轉變</td>
          <td>2016 年遷移到 microservices</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Azure Cosmos DB + microservices</td>
      </tr>
  </tbody>
</table>
<p>關鍵業務驅動：「ASOS chose Azure Cosmos DB because of its global distribution and ability to handle heavy seasonal bursts like Black Friday」。</p>
<h2 id="判讀">判讀</h2>
<p>ASOS 案例揭露三個全球零售 KV 容量規劃重點。</p>
<ol>
<li><strong>Black Friday 24h 1.67 億 = 平均 1,930 req/sec、峰值 3,500 req/sec</strong>：峰值 / 平均 = 1.81 倍。這個比例顯示 Black Friday 「持續高峰」、不是「瞬間爆量」 — 24 小時內流量曲線相對平緩、跟 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a> 的「5 分鐘賣完」是完全不同形狀。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的負載形狀識別。</li>
<li><strong>48ms 平均響應 = 全球分散下 Cosmos DB 的代表性數字</strong>：英國時尚電商、客戶遍及全球、Cosmos DB 在每個地區複製、讀取在最近 region 完成。這個 48ms 包含網路、DB、應用層 — DB 本身可能只佔 5-10ms、其他是網路與應用層。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 分解。</li>
<li><strong>85K SKU + 每週新增 5K = 高更新頻率 catalog</strong>：商品資料不只是讀、還有頻繁更新（價格、庫存、推薦排序）。這層 write throughput 對 Cosmos DB partition key 設計（通常用 category_id 或 brand_id）至關重要。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
</ol>
<p>需要警惕：這是 2016 年的數字、過去 10 年 ASOS 應該成長很多。但 1.67 億 req/24h 跟 33 orders/sec 對許多新興電商仍是天花板級數字、可作為「中大型零售」對標。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Black Friday 類「持續高峰」適合 provisioned + scheduled scaling</strong>：跟 flash-sale 的「on-demand 吃彈性」不同、Black Friday 整天高、用 provisioned 比較划算。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的可預期峰值準備。</li>
<li><strong>全球零售用 Cosmos DB / DynamoDB Global Tables</strong>：客戶在哪、讀取就在哪、避免跨洲 latency。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的全球分散取捨。</li>
<li><strong>微服務 + Cosmos DB 是電商現代化典型路徑</strong>：從單體 → 微服務、從關聯式 DB → multi-model NoSQL、是 2016 後零售業常見遷移。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 與 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
</ol>
<p>跨平台等效：AWS DynamoDB Global Tables + Lambda、GCP Firestore + Cloud Run 都可以實作對等架構。差異是 Cosmos DB 的 multi-model（同一服務支援 SQL、Mongo、Cassandra、Gremlin、Table API）、AWS 對應有 DynamoDB（KV/Document）+ Neptune（Graph）+ Keyspaces（Cassandra）等多個服務。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他可預期峰值 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a> / <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a></li>
<li>對照 flash-sale-spike → <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a></li>
<li>想對照其他 Cosmos DB 使用 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a></li>
<li>想規劃全球電商 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想拆 Black Friday 容量背後的 RU 成本與 sizing → <a href="/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/" data-link-title="Cosmos DB RU/s 成本模型 &#43; 容量規劃：RU 思維、payload、index、provisioned vs autoscale vs serverless" data-link-desc="從 CPU&#43;IOPS 思維轉到 RU 思維的學習曲線、依負載形狀選容量模式、payload &#43; index policy 對 RU 的影響、autoscale reactive 限制 — 從 ASOS Black Friday &#43; Minecraft Earth 1M RU/s 壓測切入">Cosmos DB RU 成本模型與 sizing</a></li>
<li>想做電商 partition key 設計 → <a href="/blog/backend/01-database/vendors/cosmosdb/partition-key-design/" data-link-title="Cosmos DB Partition Key Design：synthetic / composite / hierarchical &#43; 不可逆性硬約束" data-link-desc="Cosmos DB logical partition 10000 RU/s 上限、partition key 不可改、三種設計模式（synthetic / composite / hierarchical）、跟 DynamoDB / MongoDB 可逆性對比、latency budget 拆解 — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB partition key 設計</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS – Online retailer uses cloud database to deliver world-class shopping experiences</a></li>
<li><a href="https://azure.microsoft.com/en-us/products/cosmos-db/">Azure Cosmos DB</a></li>
</ul>
]]></content:encoded></item><item><title>Datadog：2023 多區觀測中斷事件</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/</guid><description>&lt;p>這起案例的核心責任是處理「監控系統本身失效」的盲區。當觀測平台中斷，事故判讀需要立即切換備援證據來源。&lt;/p>
&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>telemetry gap&lt;/td>
 &lt;td>缺失是否影響決策&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp;amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>customer-side false normal&lt;/td>
 &lt;td>客戶是否誤以為服務正常&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>fallback evidence readiness&lt;/td>
 &lt;td>備援證據能否即時接手&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「觀測資料缺失時的事故判讀」。主要風險是把缺失資料誤判為服務恢復，導致決策建立在錯誤安全感上。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>事故流程要預留「觀測失明」分支，並在復盤回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>。同時補 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20&lt;/a> 的備援證據來源。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是處理「監控系統本身失效」的盲區。當觀測平台中斷，事故判讀需要立即切換備援證據來源。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>telemetry gap</td>
          <td>缺失是否影響決策</td>
          <td><a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18</a></td>
      </tr>
      <tr>
          <td>customer-side false normal</td>
          <td>客戶是否誤以為服務正常</td>
          <td><a href="/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10</a></td>
      </tr>
      <tr>
          <td>fallback evidence readiness</td>
          <td>備援證據能否即時接手</td>
          <td><a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「觀測資料缺失時的事故判讀」。主要風險是把缺失資料誤判為服務恢復，導致決策建立在錯誤安全感上。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>事故流程要預留「觀測失明」分支，並在復盤回寫 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a>。同時補 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20</a> 的備援證據來源。</p>
]]></content:encoded></item><item><title>Honeycomb：以 Burn Rate 驅動的可靠性操作</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/</guid><description>&lt;p>Honeycomb 案例的核心責任是把可觀測訊號直接轉成可靠性決策。當團隊面對大量告警時，burn rate 提供比固定閾值更接近使用者體感的判讀方式。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>固定閾值告警在高變化流量下容易失真。團隊可能長時間處於告警疲勞，卻看不出真正侵蝕 SLO 的事件。&lt;/p>
&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>Burn rate 警示&lt;/td>
 &lt;td>可靠性消耗速度是否異常&lt;/td>
 &lt;td>優先序判讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SLO 驅動值班&lt;/td>
 &lt;td>哪些事件需要立即接手&lt;/td>
 &lt;td>響應節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tracing-first 分析&lt;/td>
 &lt;td>事件路徑如何定位&lt;/td>
 &lt;td>可追溯證據&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>fast burn&lt;/td>
 &lt;td>短期消耗是否超過容忍帶&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>slow burn&lt;/td>
 &lt;td>長期趨勢是否持續惡化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>trace outlier path&lt;/td>
 &lt;td>關鍵路徑是否集中退化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先用 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20&lt;/a> 組證據，再在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a> 回寫驗證條件。&lt;/p></description><content:encoded><![CDATA[<p>Honeycomb 案例的核心責任是把可觀測訊號直接轉成可靠性決策。當團隊面對大量告警時，burn rate 提供比固定閾值更接近使用者體感的判讀方式。</p>
<h2 id="問題場景">問題場景</h2>
<p>固定閾值告警在高變化流量下容易失真。團隊可能長時間處於告警疲勞，卻看不出真正侵蝕 SLO 的事件。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Burn rate 警示</td>
          <td>可靠性消耗速度是否異常</td>
          <td>優先序判讀</td>
      </tr>
      <tr>
          <td>SLO 驅動值班</td>
          <td>哪些事件需要立即接手</td>
          <td>響應節奏</td>
      </tr>
      <tr>
          <td>Tracing-first 分析</td>
          <td>事件路徑如何定位</td>
          <td>可追溯證據</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>fast burn</td>
          <td>短期消耗是否超過容忍帶</td>
          <td><a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6</a></td>
      </tr>
      <tr>
          <td>slow burn</td>
          <td>長期趨勢是否持續惡化</td>
          <td><a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6</a></td>
      </tr>
      <tr>
          <td>trace outlier path</td>
          <td>關鍵路徑是否集中退化</td>
          <td><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>先用 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20</a> 組證據，再在 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a> 回寫驗證條件。</p>
]]></content:encoded></item><item><title>Netflix：Steady State、Chaos 與 FIT 的驗證路徑</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/</guid><description>&lt;p>Netflix chaos 實踐的核心責任是驗證「服務在失效條件下是否仍維持 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state&lt;/a>」。重點是注入後能否用明確訊號證明系統仍可服務，故障注入數量是次要考量。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>許多團隊會做壓測與演練，但演練設計常停在工具層：kill instance、斷連線、延遲注入。這些動作本身不會自動產生可靠性結論。若沒有 steady state 與停止條件，演練只會留下「有做過 chaos」的紀錄。&lt;/p>
&lt;p>Netflix 的價值在於把 chaos 轉成科學化驗證循環：先定義穩態，再設計可證偽的假設。&lt;/p>
&lt;h2 id="決策機制">決策機制&lt;/h2>
&lt;p>一輪有效的 chaos 驗證要同時具備四個元素。&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>Steady state&lt;/td>
 &lt;td>服務正常時應維持什麼行為&lt;/td>
 &lt;td>穩態指標&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Hypothesis&lt;/td>
 &lt;td>失效發生後仍應維持什麼&lt;/td>
 &lt;td>可證偽假設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Blast radius&lt;/td>
 &lt;td>實驗範圍怎麼限制&lt;/td>
 &lt;td>實驗邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Abort condition&lt;/td>
 &lt;td>何時立即停止&lt;/td>
 &lt;td>風險切斷條件&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>FIT（Failure Injection Testing）把注入粒度推進到 request path，讓測試更接近真實依賴路徑。這讓團隊能在不擴大範圍的前提下，驗證高價值路徑的容錯能力。&lt;/p>
&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>steady-state SLI&lt;/td>
 &lt;td>注入後是否維持服務承諾&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>abort trigger count&lt;/td>
 &lt;td>停止條件是否可執行&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>fallback success ratio&lt;/td>
 &lt;td>降級與替代路徑是否有效&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>trace degradation pattern&lt;/td>
 &lt;td>退化是否集中於預期依賴&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>最常見錯誤是把 chaos 視為「故障越大越好」。這會把演練從驗證流程變成壓力展示，增加真實風險卻不提升可學習性。有效做法是用最小 blast radius 驗證最高價值假設，然後逐步放大。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>若要把本案例落地，先寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a> 的穩態欄位，再在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a> 定義停止條件。案例輸出的證據交給 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Netflix chaos 實踐的核心責任是驗證「服務在失效條件下是否仍維持 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a>」。重點是注入後能否用明確訊號證明系統仍可服務，故障注入數量是次要考量。</p>
<h2 id="問題場景">問題場景</h2>
<p>許多團隊會做壓測與演練，但演練設計常停在工具層：kill instance、斷連線、延遲注入。這些動作本身不會自動產生可靠性結論。若沒有 steady state 與停止條件，演練只會留下「有做過 chaos」的紀錄。</p>
<p>Netflix 的價值在於把 chaos 轉成科學化驗證循環：先定義穩態，再設計可證偽的假設。</p>
<h2 id="決策機制">決策機制</h2>
<p>一輪有效的 chaos 驗證要同時具備四個元素。</p>
<table>
  <thead>
      <tr>
          <th>元素</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Steady state</td>
          <td>服務正常時應維持什麼行為</td>
          <td>穩態指標</td>
      </tr>
      <tr>
          <td>Hypothesis</td>
          <td>失效發生後仍應維持什麼</td>
          <td>可證偽假設</td>
      </tr>
      <tr>
          <td>Blast radius</td>
          <td>實驗範圍怎麼限制</td>
          <td>實驗邊界</td>
      </tr>
      <tr>
          <td>Abort condition</td>
          <td>何時立即停止</td>
          <td>風險切斷條件</td>
      </tr>
  </tbody>
</table>
<p>FIT（Failure Injection Testing）把注入粒度推進到 request path，讓測試更接近真實依賴路徑。這讓團隊能在不擴大範圍的前提下，驗證高價值路徑的容錯能力。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>steady-state SLI</td>
          <td>注入後是否維持服務承諾</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>abort trigger count</td>
          <td>停止條件是否可執行</td>
          <td><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a></td>
      </tr>
      <tr>
          <td>fallback success ratio</td>
          <td>降級與替代路徑是否有效</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>trace degradation pattern</td>
          <td>退化是否集中於預期依賴</td>
          <td><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>最常見錯誤是把 chaos 視為「故障越大越好」。這會把演練從驗證流程變成壓力展示，增加真實風險卻不提升可學習性。有效做法是用最小 blast radius 驗證最高價值假設，然後逐步放大。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>若要把本案例落地，先寫 <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a> 的穩態欄位，再在 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a> 定義停止條件。案例輸出的證據交給 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a> 與 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a>。</p>
]]></content:encoded></item><item><title>Heroku</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/</guid><description>&lt;p>Heroku 是早期 PaaS 的代表、router 層事故揭露 multi-tenant 路由的失敗模式。Heroku status 與工程文章累積多年事故敘事。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Router 層失效：多租戶 PaaS 的入口失效擴散&lt;/li>
&lt;li>Dyno scheduling：背景排程系統的 failure mode&lt;/li>
&lt;li>Add-on dependency：第三方服務嵌入 PaaS 後的責任邊界&lt;/li>
&lt;li>Salesforce 收購後的 IR 演化&lt;/li>
&lt;/ul>
&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>2021&lt;/td>
 &lt;td>Router incidents&lt;/td>
 &lt;td>多租戶 PaaS 的入口失效&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2022&lt;/td>
 &lt;td>DB add-on 事故&lt;/td>
 &lt;td>第三方依賴的責任歸屬&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Heroku 這個案例在講的是 PaaS 入口路由如何成為多租戶事故的第一個放大點。讀者先抓 router、dyno scheduling 與 add-on dependency 的責任，再把 status 通訊視為事故管理的一部分。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 router 或 keepalive 機制出現問題時，事故不只影響單一應用，而會直接影響入口流量與租戶隔離。當第三方 add-on 失效時，責任邊界也要一起說清楚，否則客戶會把平台與外部依賴視為同一個故障面。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否區分 router 層與應用層問題&lt;/li>
&lt;li>能否說明 add-on 依賴的責任邊界&lt;/li>
&lt;li>能否把 incident 通訊路由到正確的 status channel&lt;/li>
&lt;li>能否把多租戶入口失效視為平台級風險&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Heroku 比較像是 PaaS 世界裡的 AWS S3 或 Cloudflare，因為入口路由一出問題，很多 tenant 會一起受影響。它也能和 Datadog、Slack 對照，幫讀者理解平台本身與平台上的應用該怎麼切責任邊界。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>router incidents 顯示入口層是多租戶 PaaS 的第一個放大器。&lt;/li>
&lt;li>DB add-on 事故則讓第三方依賴的責任邊界變得很清楚。&lt;/li>
&lt;li>keepalive 與 internal routing 會直接影響租戶體感。&lt;/li>
&lt;li>status channel 的選擇也是事故管理的一部分。&lt;/li>
&lt;li>dyno scheduling 的問題會把平台內部失衡直接變成租戶可見故障。&lt;/li>
&lt;li>Salesforce Trust 作為主通路，改變了 Heroku 事故通訊的路由方式。&lt;/li>
&lt;li>multi-tenant routing 讓入口層成為最敏感的擴散點。&lt;/li>
&lt;li>third-party add-on 事故提醒平台必須清楚切出責任邊界。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">HR1&lt;/a>&lt;/td>
 &lt;td>Routing 控制事件&lt;/td>
 &lt;td>在 PaaS 多租戶入口層限制擴散與分批回復&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://devcenter.heroku.com/articles/heroku-status">Heroku Status&lt;/a>：Heroku incident 通訊與歷史紀錄的官方說明。&lt;/li>
&lt;li>&lt;a href="https://devcenter.heroku.com/changelog-items/3422">Salesforce Trust is now the primary channel for all Heroku incident and maintenance communications&lt;/a>：Heroku status 通訊的最新主通路。&lt;/li>
&lt;li>&lt;a href="https://devcenter.heroku.com/articles/heroku-labs-disabling-keepalives-to-dyno-for-router-2-0">Heroku Labs: Disabling Keepalives to Dyno for the Common Runtime Router&lt;/a>：Router / keepalive 的官方設計說明。&lt;/li>
&lt;li>&lt;a href="https://devcenter.heroku.com/articles/internal-routing">Internal Routing&lt;/a>：PaaS 內部路由與多租戶邊界。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Heroku 是早期 PaaS 的代表、router 層事故揭露 multi-tenant 路由的失敗模式。Heroku status 與工程文章累積多年事故敘事。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Router 層失效：多租戶 PaaS 的入口失效擴散</li>
<li>Dyno scheduling：背景排程系統的 failure mode</li>
<li>Add-on dependency：第三方服務嵌入 PaaS 後的責任邊界</li>
<li>Salesforce 收購後的 IR 演化</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2021</td>
          <td>Router incidents</td>
          <td>多租戶 PaaS 的入口失效</td>
      </tr>
      <tr>
          <td>2022</td>
          <td>DB add-on 事故</td>
          <td>第三方依賴的責任歸屬</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Heroku 這個案例在講的是 PaaS 入口路由如何成為多租戶事故的第一個放大點。讀者先抓 router、dyno scheduling 與 add-on dependency 的責任，再把 status 通訊視為事故管理的一部分。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 router 或 keepalive 機制出現問題時，事故不只影響單一應用，而會直接影響入口流量與租戶隔離。當第三方 add-on 失效時，責任邊界也要一起說清楚，否則客戶會把平台與外部依賴視為同一個故障面。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否區分 router 層與應用層問題</li>
<li>能否說明 add-on 依賴的責任邊界</li>
<li>能否把 incident 通訊路由到正確的 status channel</li>
<li>能否把多租戶入口失效視為平台級風險</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Heroku 比較像是 PaaS 世界裡的 AWS S3 或 Cloudflare，因為入口路由一出問題，很多 tenant 會一起受影響。它也能和 Datadog、Slack 對照，幫讀者理解平台本身與平台上的應用該怎麼切責任邊界。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>router incidents 顯示入口層是多租戶 PaaS 的第一個放大器。</li>
<li>DB add-on 事故則讓第三方依賴的責任邊界變得很清楚。</li>
<li>keepalive 與 internal routing 會直接影響租戶體感。</li>
<li>status channel 的選擇也是事故管理的一部分。</li>
<li>dyno scheduling 的問題會把平台內部失衡直接變成租戶可見故障。</li>
<li>Salesforce Trust 作為主通路，改變了 Heroku 事故通訊的路由方式。</li>
<li>multi-tenant routing 讓入口層成為最敏感的擴散點。</li>
<li>third-party add-on 事故提醒平台必須清楚切出責任邊界。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">HR1</a></td>
          <td>Routing 控制事件</td>
          <td>在 PaaS 多租戶入口層限制擴散與分批回復</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://devcenter.heroku.com/articles/heroku-status">Heroku Status</a>：Heroku incident 通訊與歷史紀錄的官方說明。</li>
<li><a href="https://devcenter.heroku.com/changelog-items/3422">Salesforce Trust is now the primary channel for all Heroku incident and maintenance communications</a>：Heroku status 通訊的最新主通路。</li>
<li><a href="https://devcenter.heroku.com/articles/heroku-labs-disabling-keepalives-to-dyno-for-router-2-0">Heroku Labs: Disabling Keepalives to Dyno for the Common Runtime Router</a>：Router / keepalive 的官方設計說明。</li>
<li><a href="https://devcenter.heroku.com/articles/internal-routing">Internal Routing</a>：PaaS 內部路由與多租戶邊界。</li>
</ul>
]]></content:encoded></item><item><title>Spotify</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/</guid><description>&lt;p>Spotify 是音樂串流平台、squad-based 組織模型對 SRE 實踐有特殊影響、chaos engineering 文章是 mid-size company 採用 chaos 的代表。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Squad-based SRE：分散式組織下的可靠性責任分配&lt;/li>
&lt;li>Backstage：開源開發者平台的可靠性整合&lt;/li>
&lt;li>Chaos engineering 採用過程：從 zero 到 mature 的實踐軌跡&lt;/li>
&lt;li>Streaming infrastructure：高頻寬媒體的可靠性挑戰&lt;/li>
&lt;/ul>
&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>Backstage&lt;/td>
 &lt;td>service catalog + reliability metadata&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Squad SRE&lt;/td>
 &lt;td>分散組織的可靠性責任&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Chaos Engineering Adoption&lt;/td>
 &lt;td>Spotify 的 chaos 起步歷程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CDN / Streaming Resilience&lt;/td>
 &lt;td>媒體串流的失敗模式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Spotify 這個案例在講的是平台工程如何把可靠性散到每個 squad，又把共同能力集中到 Backstage 這類基礎設施。讀者先抓 squad-based SRE、service catalog 與 declarative infrastructure 的關係，再看它們怎麼支撐大型串流平台。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當組織採用分散責任模型時，可靠性不再只靠中央團隊，而是靠平台把常見能力做成共同元件。當 fleet 或 streaming 基礎設施需要治理時，重點是 catalog 與 control plane 是否讓團隊看得到、管得動。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否把 service catalog 跟 reliability metadata 接起來&lt;/li>
&lt;li>能否說清楚 squad 與平台各自負責什麼&lt;/li>
&lt;li>能否用 declarative infrastructure 管 fleet 變化&lt;/li>
&lt;li>能否在 chaos 採用時保住平台一致性&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Spotify 的重點是把可靠性做成平台能力，這和 LinkedIn 的 operability、Honeycomb 的 observability、Meta 的 control plane 治理屬於相近抽象層。不同的是 Spotify 更強調組織分工，所以很適合拿來說明平台如何支撐分散團隊。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>Backstage 將 service catalog 與 reliability metadata 整合成平台入口。&lt;/li>
&lt;li>declarative infrastructure 讓 fleet 管理變成可重現的控制流程。&lt;/li>
&lt;li>squad-based SRE 讓責任分散到服務團隊。&lt;/li>
&lt;li>chaos engineering adoption 讓平台能力和演練節奏一起成熟。&lt;/li>
&lt;li>streaming resilience 讓高頻寬服務的失敗模式能被平台化管理。&lt;/li>
&lt;li>service catalog 讓可靠性資訊跟服務拓撲一起被看見。&lt;/li>
&lt;li>fleet management 讓大規模機器與服務狀態保持一致。&lt;/li>
&lt;li>catalog-driven ops 讓平台資訊成為日常營運入口。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">SP1&lt;/a>&lt;/td>
 &lt;td>平台工程與可靠性契約&lt;/td>
 &lt;td>讓分散團隊共用可靠性基線與交付契約&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/" data-link-title="Spotify：Backstage Service Catalog 與 Reliability Metadata" data-link-desc="用 service catalog 治理分散團隊的可靠性資訊：ownership、SLO 狀態、依賴圖與 runbook 的單一入口。">SP2&lt;/a>&lt;/td>
 &lt;td>Backstage Service Catalog 與 Reliability Metadata&lt;/td>
 &lt;td>用 service catalog 治理分散團隊的可靠性資訊&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.atspotify.com/about/">About | Spotify Engineering&lt;/a>：Spotify Engineering 與 Backstage 的官方入口。&lt;/li>
&lt;li>&lt;a href="https://backstage.io/blog/2020/03/16/announcing-backstage">Announcing Backstage&lt;/a>：Backstage 的開源宣布與背景。&lt;/li>
&lt;li>&lt;a href="https://backstage.io/docs/overview/technical-overview">Technical overview&lt;/a>：Backstage 的技術總覽與 catalog/portal 說明。&lt;/li>
&lt;li>&lt;a href="https://engineering.atspotify.com/2023/05/fleet-management-at-spotify-part-2-the-path-to-declarative-infrastructure/">Fleet Management at Spotify (Part 2): The Path to Declarative Infrastructure&lt;/a>：大規模 fleet 與控制面的治理。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Spotify 是音樂串流平台、squad-based 組織模型對 SRE 實踐有特殊影響、chaos engineering 文章是 mid-size company 採用 chaos 的代表。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Squad-based SRE：分散式組織下的可靠性責任分配</li>
<li>Backstage：開源開發者平台的可靠性整合</li>
<li>Chaos engineering 採用過程：從 zero 到 mature 的實踐軌跡</li>
<li>Streaming infrastructure：高頻寬媒體的可靠性挑戰</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Backstage</td>
          <td>service catalog + reliability metadata</td>
      </tr>
      <tr>
          <td>Squad SRE</td>
          <td>分散組織的可靠性責任</td>
      </tr>
      <tr>
          <td>Chaos Engineering Adoption</td>
          <td>Spotify 的 chaos 起步歷程</td>
      </tr>
      <tr>
          <td>CDN / Streaming Resilience</td>
          <td>媒體串流的失敗模式</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Spotify 這個案例在講的是平台工程如何把可靠性散到每個 squad，又把共同能力集中到 Backstage 這類基礎設施。讀者先抓 squad-based SRE、service catalog 與 declarative infrastructure 的關係，再看它們怎麼支撐大型串流平台。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當組織採用分散責任模型時，可靠性不再只靠中央團隊，而是靠平台把常見能力做成共同元件。當 fleet 或 streaming 基礎設施需要治理時，重點是 catalog 與 control plane 是否讓團隊看得到、管得動。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否把 service catalog 跟 reliability metadata 接起來</li>
<li>能否說清楚 squad 與平台各自負責什麼</li>
<li>能否用 declarative infrastructure 管 fleet 變化</li>
<li>能否在 chaos 採用時保住平台一致性</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Spotify 的重點是把可靠性做成平台能力，這和 LinkedIn 的 operability、Honeycomb 的 observability、Meta 的 control plane 治理屬於相近抽象層。不同的是 Spotify 更強調組織分工，所以很適合拿來說明平台如何支撐分散團隊。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>Backstage 將 service catalog 與 reliability metadata 整合成平台入口。</li>
<li>declarative infrastructure 讓 fleet 管理變成可重現的控制流程。</li>
<li>squad-based SRE 讓責任分散到服務團隊。</li>
<li>chaos engineering adoption 讓平台能力和演練節奏一起成熟。</li>
<li>streaming resilience 讓高頻寬服務的失敗模式能被平台化管理。</li>
<li>service catalog 讓可靠性資訊跟服務拓撲一起被看見。</li>
<li>fleet management 讓大規模機器與服務狀態保持一致。</li>
<li>catalog-driven ops 讓平台資訊成為日常營運入口。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">SP1</a></td>
          <td>平台工程與可靠性契約</td>
          <td>讓分散團隊共用可靠性基線與交付契約</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/" data-link-title="Spotify：Backstage Service Catalog 與 Reliability Metadata" data-link-desc="用 service catalog 治理分散團隊的可靠性資訊：ownership、SLO 狀態、依賴圖與 runbook 的單一入口。">SP2</a></td>
          <td>Backstage Service Catalog 與 Reliability Metadata</td>
          <td>用 service catalog 治理分散團隊的可靠性資訊</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/about/">About | Spotify Engineering</a>：Spotify Engineering 與 Backstage 的官方入口。</li>
<li><a href="https://backstage.io/blog/2020/03/16/announcing-backstage">Announcing Backstage</a>：Backstage 的開源宣布與背景。</li>
<li><a href="https://backstage.io/docs/overview/technical-overview">Technical overview</a>：Backstage 的技術總覽與 catalog/portal 說明。</li>
<li><a href="https://engineering.atspotify.com/2023/05/fleet-management-at-spotify-part-2-the-path-to-declarative-infrastructure/">Fleet Management at Spotify (Part 2): The Path to Declarative Infrastructure</a>：大規模 fleet 與控制面的治理。</li>
</ul>
]]></content:encoded></item><item><title>3.C22 Trivago：KEDA scale-to-zero by Kafka lag</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/</guid><description>&lt;p>這個案例的核心責任是說明 event-driven workload 該按 backlog 而非 resource usage scale 的設計判準。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Trivago 跨 3 個 region 跑 50+ Kafka sink service、每個 always-on 用 1 CPU + 1 GB；CPU/mem-based autoscaling 無效（sink 多為 I/O bottleneck、CPU 平坦）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>KEDA 以 consumer lag 為 scaling signal、minReplicaCount=0 達到 scale-to-zero、daily replica-hour 從 50 降到 1-2。揭露「resource usage 不等於工作量」、event-driven 場景該看 backlog signal。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：consumer lag / autoscaling / multi-tenant 配額。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tech.trivago.com/post/2026-02-18-from-always-on-to-on-demand-scaling-kafka-sinks-with-keda">From Always-On to On-Demand: Scaling Kafka Sinks with KEDA&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 event-driven workload 該按 backlog 而非 resource usage scale 的設計判準。</p>
<h2 id="觀察">觀察</h2>
<p>Trivago 跨 3 個 region 跑 50+ Kafka sink service、每個 always-on 用 1 CPU + 1 GB；CPU/mem-based autoscaling 無效（sink 多為 I/O bottleneck、CPU 平坦）。</p>
<h2 id="判讀">判讀</h2>
<p>KEDA 以 consumer lag 為 scaling signal、minReplicaCount=0 達到 scale-to-zero、daily replica-hour 從 50 降到 1-2。揭露「resource usage 不等於工作量」、event-driven 場景該看 backlog signal。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：consumer lag / autoscaling / multi-tenant 配額。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://tech.trivago.com/post/2026-02-18-from-always-on-to-on-demand-scaling-kafka-sinks-with-keda">From Always-On to On-Demand: Scaling Kafka Sinks with KEDA</a></li>
</ul>
]]></content:encoded></item><item><title>9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/</guid><description>&lt;p>這個案例的核心責任是說明「hybrid cloud burst」模式 — 平日跑自家 data center、峰值事件靠雲端補容量。這跟全部上雲（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a>）或全部自管的兩種極端都不同、是大企業常見的折衷路徑。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wayfair 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study&lt;/a>）：&lt;/p>
&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>22 M+ 個 SKU&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>供應商數量&lt;/td>
 &lt;td>16,000+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>員工數&lt;/td>
 &lt;td>17,000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>北美 + 歐洲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值事件&lt;/td>
 &lt;td>Way Day（年度大促）、Black Friday、Cyber Monday&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>COVID Q2 2020 業績&lt;/td>
 &lt;td>美國淨營收成長 +82.5%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構模式&lt;/td>
 &lt;td>Hybrid（on-prem + GCP burst）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：BigQuery（資料倉儲）、Cloud Dataproc（資料處理）、Cloud Pub/Sub（資料注入）、Looker（dashboard）、Cloud DLP（合規）、C2 processors（高性能 compute）。&lt;/p>
&lt;p>關鍵敘述：「Our automation systems signal the cloud to scale on demand」「We were able to reduce and eventually eliminate the need for change freezes leading up to big events」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Wayfair 揭露三個 hybrid cloud burst 模式的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Hybrid burst 是「容量規劃成本平衡」的折衷&lt;/strong>：自家 data center 平日跑得便宜、峰值事件不夠用；全部上雲峰值好辦但平日成本高。Hybrid 模式讓 baseline 用便宜的、峰值用彈性的、總成本曲線最平。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的長期 TCO 規劃。&lt;/li>
&lt;li>&lt;strong>「Change freeze 不再需要」是 burst 模式的真正價值&lt;/strong>：傳統零售 IT 為了 Black Friday 通常 2-3 個月前就 freeze code change、確保穩定。Wayfair 在 GCP burst 上線後、能在峰值前繼續正常 release — 因為新功能可以單獨 deploy 到 GCP、不影響 on-prem 主系統。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate&lt;/a> 的非凍結式變更管理。&lt;/li>
&lt;li>&lt;strong>資料平面（BigQuery / Dataproc）是 hybrid 的主場、交易平面仍在 on-prem&lt;/strong>：Wayfair 把「分析、報表、推薦模型」放 GCP、「核心交易、訂單處理、庫存」仍在自家。這個切分是 hybrid 的常見做法 — 計算密集的工作上雲、業務核心保留自管。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的核心 OLTP 跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的分析資料層分離。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>Wayfair 案例 &lt;em>沒有&lt;/em> 提具體 TPS、latency、capacity scale 數字 — 行銷敘述居多、工程細節較少。讀此類案例要對 &lt;em>策略&lt;/em> 做學習、不要套用具體數字。&lt;/li>
&lt;li>「82.5% 美國淨營收成長」是 &lt;em>業績&lt;/em>、不是 &lt;em>系統指標&lt;/em>。系統能撐業績、但兩者不是同一件事。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「hybrid cloud burst」模式 — 平日跑自家 data center、峰值事件靠雲端補容量。這跟全部上雲（<a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>）或全部自管的兩種極端都不同、是大企業常見的折衷路徑。</p>
<h2 id="觀察">觀察</h2>
<p>Wayfair 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>商品數量</td>
          <td>22 M+ 個 SKU</td>
      </tr>
      <tr>
          <td>供應商數量</td>
          <td>16,000+</td>
      </tr>
      <tr>
          <td>員工數</td>
          <td>17,000</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>北美 + 歐洲</td>
      </tr>
      <tr>
          <td>峰值事件</td>
          <td>Way Day（年度大促）、Black Friday、Cyber Monday</td>
      </tr>
      <tr>
          <td>COVID Q2 2020 業績</td>
          <td>美國淨營收成長 +82.5%</td>
      </tr>
      <tr>
          <td>架構模式</td>
          <td>Hybrid（on-prem + GCP burst）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：BigQuery（資料倉儲）、Cloud Dataproc（資料處理）、Cloud Pub/Sub（資料注入）、Looker（dashboard）、Cloud DLP（合規）、C2 processors（高性能 compute）。</p>
<p>關鍵敘述：「Our automation systems signal the cloud to scale on demand」「We were able to reduce and eventually eliminate the need for change freezes leading up to big events」。</p>
<h2 id="判讀">判讀</h2>
<p>Wayfair 揭露三個 hybrid cloud burst 模式的工程重點。</p>
<ol>
<li><strong>Hybrid burst 是「容量規劃成本平衡」的折衷</strong>：自家 data center 平日跑得便宜、峰值事件不夠用；全部上雲峰值好辦但平日成本高。Hybrid 模式讓 baseline 用便宜的、峰值用彈性的、總成本曲線最平。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的長期 TCO 規劃。</li>
<li><strong>「Change freeze 不再需要」是 burst 模式的真正價值</strong>：傳統零售 IT 為了 Black Friday 通常 2-3 個月前就 freeze code change、確保穩定。Wayfair 在 GCP burst 上線後、能在峰值前繼續正常 release — 因為新功能可以單獨 deploy 到 GCP、不影響 on-prem 主系統。對應 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a> 的非凍結式變更管理。</li>
<li><strong>資料平面（BigQuery / Dataproc）是 hybrid 的主場、交易平面仍在 on-prem</strong>：Wayfair 把「分析、報表、推薦模型」放 GCP、「核心交易、訂單處理、庫存」仍在自家。這個切分是 hybrid 的常見做法 — 計算密集的工作上雲、業務核心保留自管。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的核心 OLTP 跟 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的分析資料層分離。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>Wayfair 案例 <em>沒有</em> 提具體 TPS、latency、capacity scale 數字 — 行銷敘述居多、工程細節較少。讀此類案例要對 <em>策略</em> 做學習、不要套用具體數字。</li>
<li>「82.5% 美國淨營收成長」是 <em>業績</em>、不是 <em>系統指標</em>。系統能撐業績、但兩者不是同一件事。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Hybrid burst 適合「業務核心 on-prem 已穩定 + 季節性 / 事件型峰值」的企業</strong>：對於全新雲原生 startup、直接全上雲更簡單；對於有 15-20 年自建系統的大企業、hybrid 是穩妥路徑。</li>
<li><strong>資料平面先上雲、交易平面後上</strong>：BI、ML、推薦這類「計算密集 + 資料量大 + 容忍延遲」適合先上 GCP / AWS / Azure；OLTP 後續再評估。對應 <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a> 的資料層先行模式。</li>
<li><strong>automation signal + 雲端 burst 是「change freeze」的解法</strong>：監控訊號 → 自動 trigger 雲端容量 → 平滑釋放 → 不影響 on-prem 主系統的部署節奏。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
</ol>
<p>跨平台等效：AWS Outposts + AWS Direct Connect、Azure Arc + ExpressRoute、Equinix + 各雲商 PrivateLink 都是 hybrid burst 的基礎設施。差異是各家 hybrid 策略成熟度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 hybrid cloud burst → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>想做資料平面遷移 → <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>對照全雲原生 → <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a></li>
<li>想取消 change freeze → <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a> + <a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">06.17 feature flag governance</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study (Google Cloud)</a></li>
<li><a href="https://cloud.google.com/blog/topics/customers">Way Day 2019 burst capacity</a></li>
</ul>
]]></content:encoded></item><item><title>Netflix：Business-Hours Chaos 與 Guardrails</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/</guid><description>&lt;p>Netflix 把 Chaos Monkey 放在 business hours 執行，核心責任是同時驗證系統韌性與團隊反應能力。若只在離峰或隔離環境跑故障注入，很多真實依賴與協作問題不會被看見。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>團隊常把 chaos 排在低流量時段，理由是比較安全。這種做法雖然降低短期風險，但也降低驗證價值：人員不在位、依賴流量特徵不同、通訊鏈條沒被真正測到。最後得到的是工具可執行，不是服務可承受。&lt;/p>
&lt;h2 id="驗證機制">驗證機制&lt;/h2>
&lt;p>Business-hours chaos 是把風險放進 guardrails 內驗證，風險範圍是收斂的。&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>時段限制&lt;/td>
 &lt;td>事故處理人力是否在線&lt;/td>
 &lt;td>僅在可支援時段啟動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實驗範圍限制&lt;/td>
 &lt;td>是否影響過大 blast radius&lt;/td>
 &lt;td>先從小範圍服務群組啟動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>停止條件&lt;/td>
 &lt;td>何時立即結束實驗&lt;/td>
 &lt;td>明確 abort trigger 與 rollback 路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事後回寫&lt;/td>
 &lt;td>是否有把結果回寫到工程控制面&lt;/td>
 &lt;td>固定接 [8.22 evidence write-back]&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個機制的本質是「在可控邊界內接近真實情境」，而不是追求更大故障。&lt;/p>
&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>abort trigger latency&lt;/td>
 &lt;td>停止條件是否能即時生效&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>on-call handoff quality&lt;/td>
 &lt;td>值班與指揮鏈條是否順暢&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>steady-state drift&lt;/td>
 &lt;td>實驗期間是否偏離穩態&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>communication lag&lt;/td>
 &lt;td>內外部更新是否跟上變化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>常見誤解是「business hours chaos 比較危險，所以應該避免」。真正風險在於沒有 guardrails，而不是時段本身。若有明確範圍、停止條件與值班協調，business-hours 測到的結果反而更接近真實事故。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 Reliability Readiness Review&lt;/a> 檢查實驗前置條件，再到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a> 寫 guardrails 與 abort 條件。實驗結果回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6 Drills and On-call Readiness&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey">Netflix/SimianArmy Wiki: Chaos Monkey&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Netflix 把 Chaos Monkey 放在 business hours 執行，核心責任是同時驗證系統韌性與團隊反應能力。若只在離峰或隔離環境跑故障注入，很多真實依賴與協作問題不會被看見。</p>
<h2 id="問題場景">問題場景</h2>
<p>團隊常把 chaos 排在低流量時段，理由是比較安全。這種做法雖然降低短期風險，但也降低驗證價值：人員不在位、依賴流量特徵不同、通訊鏈條沒被真正測到。最後得到的是工具可執行，不是服務可承受。</p>
<h2 id="驗證機制">驗證機制</h2>
<p>Business-hours chaos 是把風險放進 guardrails 內驗證，風險範圍是收斂的。</p>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>控制方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>時段限制</td>
          <td>事故處理人力是否在線</td>
          <td>僅在可支援時段啟動</td>
      </tr>
      <tr>
          <td>實驗範圍限制</td>
          <td>是否影響過大 blast radius</td>
          <td>先從小範圍服務群組啟動</td>
      </tr>
      <tr>
          <td>停止條件</td>
          <td>何時立即結束實驗</td>
          <td>明確 abort trigger 與 rollback 路徑</td>
      </tr>
      <tr>
          <td>事後回寫</td>
          <td>是否有把結果回寫到工程控制面</td>
          <td>固定接 [8.22 evidence write-back]</td>
      </tr>
  </tbody>
</table>
<p>這個機制的本質是「在可控邊界內接近真實情境」，而不是追求更大故障。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>abort trigger latency</td>
          <td>停止條件是否能即時生效</td>
          <td><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a></td>
      </tr>
      <tr>
          <td>on-call handoff quality</td>
          <td>值班與指揮鏈條是否順暢</td>
          <td><a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2</a></td>
      </tr>
      <tr>
          <td>steady-state drift</td>
          <td>實驗期間是否偏離穩態</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>communication lag</td>
          <td>內外部更新是否跟上變化</td>
          <td><a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>常見誤解是「business hours chaos 比較危險，所以應該避免」。真正風險在於沒有 guardrails，而不是時段本身。若有明確範圍、停止條件與值班協調，business-hours 測到的結果反而更接近真實事故。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先在 <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 Reliability Readiness Review</a> 檢查實驗前置條件，再到 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a> 寫 guardrails 與 abort 條件。實驗結果回寫 <a href="/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6 Drills and On-call Readiness</a> 與 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey">Netflix/SimianArmy Wiki: Chaos Monkey</a></li>
<li><a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey</a></li>
</ul>
]]></content:encoded></item><item><title>Pinterest</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/</guid><description>&lt;p>Pinterest 是視覺探索平台、capacity planning 與儲存架構的工程文章揭露大規模 data-heavy service 的可靠性挑戰。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Storage Capacity：HBase / TiDB 等 stateful 系統的 capacity model&lt;/li>
&lt;li>Cache Strategies：Memcache / Redis 大規模部署的 failure mode&lt;/li>
&lt;li>Scaling Patterns：visual search 等高運算服務的可靠性&lt;/li>
&lt;li>Migration Reliability：跨 storage backend migration 的零事故設計&lt;/li>
&lt;/ul>
&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>Storage Migration&lt;/td>
 &lt;td>HBase → TiDB 等大規模 migration 設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cache Reliability&lt;/td>
 &lt;td>hot key、thundering herd 的工程處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Capacity Planning&lt;/td>
 &lt;td>data-heavy service 的容量預測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML Serving Resilience&lt;/td>
 &lt;td>推薦系統的可靠性需求&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Pinterest 這個案例在講的是資料密集型服務如何透過 storage migration 與容量規劃維持可用性。讀者先抓 HBase、TiDB、zero downtime migration 與 RocksDB 這些原語，再把它們視為資料平台演進的路徑。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當儲存後端需要退役或升級時，重點是如何在搬移過程中維持服務穩定，把資料搬過去只是其中一環。當推薦或搜尋系統吃到熱點流量時，cache 與 capacity 的設計要先保住查詢路徑，再處理最佳化。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否把 storage migration 拆成不中斷的階段&lt;/li>
&lt;li>能否指出 hot key 與 thundering herd 的風險位置&lt;/li>
&lt;li>能否讓 data platform 的容量模型跟業務成長對齊&lt;/li>
&lt;li>能否把 migration 成果寫成可重複的工程模式&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Pinterest 把資料平台演進和可靠性綁在一起，和 Shopify 的峰值準備、GitHub 的資料一致性、Meta 的大規模 storage 實踐都有對照價值。這頁最重要的訊息是：migration 是維持服務語義的持續變更，用搬家的心態做會忽略穩定性。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>HBase → TiDB migration 展示零停機遷移如何保住線上讀寫。&lt;/li>
&lt;li>RocksDB wide column database 代表新 storage backend 如何接手舊系統的壓力。&lt;/li>
&lt;li>cache strategies 讓熱點流量不直接壓垮主存儲。&lt;/li>
&lt;li>capacity planning 把資料密集型服務的擴容節奏固定下來。&lt;/li>
&lt;li>ML serving resilience 讓推薦系統在資料平台變動時仍能維持體感。&lt;/li>
&lt;li>zero-downtime migration 讓線上變更從一次性事件變成可管理流程。&lt;/li>
&lt;li>hot key mitigation 讓快取與查詢壓力不會一起炸開。&lt;/li>
&lt;li>storage backend migration 讓資料平台可以分階段換血。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">P1&lt;/a>&lt;/td>
 &lt;td>快取可靠性與容量驚奇&lt;/td>
 &lt;td>在命中率崩落時維持可回復節奏與容量緩衝&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/" data-link-title="Pinterest：Storage Migration 與 Data Infrastructure Reliability" data-link-desc="大規模儲存遷移的可靠性設計：用 dual-write、shadow read 與 staged cutover 讓 PB 級資料基礎設施變更可漸進、可驗證、可回退。">P2&lt;/a>&lt;/td>
 &lt;td>Storage Migration 與 Data Infrastructure&lt;/td>
 &lt;td>大規模儲存遷移的漸進驗證與 dual-write / shadow read&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/hbase-deprecation-at-pinterest-8a99e6c8e6b7">HBase Deprecation at Pinterest&lt;/a>：HBase 退役與新 storage 方向。&lt;/li>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/tidb-adoption-at-pinterest-1130ab787a10">TiDB Adoption at Pinterest&lt;/a>：TiDB 選型與 migration 脈絡。&lt;/li>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/online-data-migration-from-hbase-to-tidb-with-zero-downtime-43f0fb474b84">Online Data Migration from HBase to TiDB with Zero Downtime&lt;/a>：零停機遷移的具體實作。&lt;/li>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/building-pinterests-new-wide-column-database-using-rocksdb-f5277ee4e3d2">Building Pinterest’s new wide column database using RocksDB&lt;/a>：新 wide column database 的工程脈絡。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Pinterest 是視覺探索平台、capacity planning 與儲存架構的工程文章揭露大規模 data-heavy service 的可靠性挑戰。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Storage Capacity：HBase / TiDB 等 stateful 系統的 capacity model</li>
<li>Cache Strategies：Memcache / Redis 大規模部署的 failure mode</li>
<li>Scaling Patterns：visual search 等高運算服務的可靠性</li>
<li>Migration Reliability：跨 storage backend migration 的零事故設計</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Storage Migration</td>
          <td>HBase → TiDB 等大規模 migration 設計</td>
      </tr>
      <tr>
          <td>Cache Reliability</td>
          <td>hot key、thundering herd 的工程處理</td>
      </tr>
      <tr>
          <td>Capacity Planning</td>
          <td>data-heavy service 的容量預測</td>
      </tr>
      <tr>
          <td>ML Serving Resilience</td>
          <td>推薦系統的可靠性需求</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Pinterest 這個案例在講的是資料密集型服務如何透過 storage migration 與容量規劃維持可用性。讀者先抓 HBase、TiDB、zero downtime migration 與 RocksDB 這些原語，再把它們視為資料平台演進的路徑。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當儲存後端需要退役或升級時，重點是如何在搬移過程中維持服務穩定，把資料搬過去只是其中一環。當推薦或搜尋系統吃到熱點流量時，cache 與 capacity 的設計要先保住查詢路徑，再處理最佳化。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否把 storage migration 拆成不中斷的階段</li>
<li>能否指出 hot key 與 thundering herd 的風險位置</li>
<li>能否讓 data platform 的容量模型跟業務成長對齊</li>
<li>能否把 migration 成果寫成可重複的工程模式</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Pinterest 把資料平台演進和可靠性綁在一起，和 Shopify 的峰值準備、GitHub 的資料一致性、Meta 的大規模 storage 實踐都有對照價值。這頁最重要的訊息是：migration 是維持服務語義的持續變更，用搬家的心態做會忽略穩定性。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>HBase → TiDB migration 展示零停機遷移如何保住線上讀寫。</li>
<li>RocksDB wide column database 代表新 storage backend 如何接手舊系統的壓力。</li>
<li>cache strategies 讓熱點流量不直接壓垮主存儲。</li>
<li>capacity planning 把資料密集型服務的擴容節奏固定下來。</li>
<li>ML serving resilience 讓推薦系統在資料平台變動時仍能維持體感。</li>
<li>zero-downtime migration 讓線上變更從一次性事件變成可管理流程。</li>
<li>hot key mitigation 讓快取與查詢壓力不會一起炸開。</li>
<li>storage backend migration 讓資料平台可以分階段換血。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">P1</a></td>
          <td>快取可靠性與容量驚奇</td>
          <td>在命中率崩落時維持可回復節奏與容量緩衝</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/" data-link-title="Pinterest：Storage Migration 與 Data Infrastructure Reliability" data-link-desc="大規模儲存遷移的可靠性設計：用 dual-write、shadow read 與 staged cutover 讓 PB 級資料基礎設施變更可漸進、可驗證、可回退。">P2</a></td>
          <td>Storage Migration 與 Data Infrastructure</td>
          <td>大規模儲存遷移的漸進驗證與 dual-write / shadow read</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/hbase-deprecation-at-pinterest-8a99e6c8e6b7">HBase Deprecation at Pinterest</a>：HBase 退役與新 storage 方向。</li>
<li><a href="https://medium.com/pinterest-engineering/tidb-adoption-at-pinterest-1130ab787a10">TiDB Adoption at Pinterest</a>：TiDB 選型與 migration 脈絡。</li>
<li><a href="https://medium.com/pinterest-engineering/online-data-migration-from-hbase-to-tidb-with-zero-downtime-43f0fb474b84">Online Data Migration from HBase to TiDB with Zero Downtime</a>：零停機遷移的具體實作。</li>
<li><a href="https://medium.com/pinterest-engineering/building-pinterests-new-wide-column-database-using-rocksdb-f5277ee4e3d2">Building Pinterest’s new wide column database using RocksDB</a>：新 wide column database 的工程脈絡。</li>
</ul>
]]></content:encoded></item><item><title>Reddit</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/</guid><description>&lt;p>Reddit 2023 Pi Day（3/14）的 314 分鐘事故是 Kubernetes 升級導致的事故、揭露 k8s 升級在大規模生產環境的隱性風險。Reddit engineering blog 公開 post-mortem 細節豐富。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>Kubernetes 升級風險：minor version 升級的 breaking change&lt;/li>
&lt;li>升級回滾困境：為何 k8s control plane 不能直接降版&lt;/li>
&lt;li>大規模 stateful workload 的特殊性：pod 重排對狀態服務的衝擊&lt;/li>
&lt;li>內部 IR 流程：Reddit 的 IR commander / scribe 結構公開度&lt;/li>
&lt;/ul>
&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>2023-03&lt;/td>
 &lt;td>Pi Day k8s 升級 314 分鐘&lt;/td>
 &lt;td>k8s upgrade、control plane 回滾困境&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Reddit 這個案例在講的是 Kubernetes 升級如何在大規模 stateful 工作負載上拉長事故。讀者先看懂控制平面升級、回滾限制與狀態服務的特性，再把 Pi Day outage 當成升級風險的具體樣本。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 control plane 進行升級時，最先要保住的是回滾空間與資料完整性。當 pod 重排碰到 stateful workload 時，恢復節奏就不能只看節點健康，而要看整個狀態層是否真的穩回來。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否判斷問題是在 k8s 升級還是 workload 本身&lt;/li>
&lt;li>能否把回滾限制與控制平面風險講清楚&lt;/li>
&lt;li>能否辨識 stateful workload 的額外恢復成本&lt;/li>
&lt;li>能否把 IR commander / scribe 的流程用在對外說明&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Reddit 和 GitHub、Heroku 的交集在於，它們都會把平台層變更直接反映成使用者可見的 outage。這頁最值得和 GCP 一起看，因為 Kubernetes 升級與 control plane 回滾問題，能很好地補足「服務自己沒有寫錯，但平台還是會出事」這個視角。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2023-03 Pi Day 314 分鐘事故是 k8s 升級與 stateful workload 互相放大的樣本。&lt;/li>
&lt;li>這類事件特別能看出 control plane 回滾為何比一般服務回滾更麻煩。&lt;/li>
&lt;li>IR commander / scribe 讓對外資訊流有固定節奏。&lt;/li>
&lt;li>k8s 升級風險和其他平台事故頁可以互相對照。&lt;/li>
&lt;li>stateful workload 的 pod 重排會把效能恢復拉長。&lt;/li>
&lt;li>control plane rollback 的限制讓升級決策必須更早做完。&lt;/li>
&lt;li>kube upgrade 是整個平台控制面的變更，用版本更新的心態處理會低估風險。&lt;/li>
&lt;li>stateful service 的 cold start 會把恢復時間拉長到使用者可感知的程度。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">RD1&lt;/a>&lt;/td>
 &lt;td>Kubernetes 升級事故&lt;/td>
 &lt;td>將平台升級變更納入事故分級與回退節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.redditstatus.com/">Reddit Status&lt;/a>：Reddit 狀態頁與 incident history。&lt;/li>
&lt;li>&lt;a href="https://www.redditstatus.com/history">Reddit Status - Incident History&lt;/a>：歷史事故與 uptime 檢視。&lt;/li>
&lt;li>&lt;a href="https://www.redditstatus.com/api">Reddit Status - API&lt;/a>：status page API 文件。&lt;/li>
&lt;li>&lt;a href="https://redditinc.com/blog/the-search-for-better-search-at-reddit">The Search for Better Search at Reddit&lt;/a>：Reddit 工程內容總入口之一，補基礎工程脈絡。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Reddit 2023 Pi Day（3/14）的 314 分鐘事故是 Kubernetes 升級導致的事故、揭露 k8s 升級在大規模生產環境的隱性風險。Reddit engineering blog 公開 post-mortem 細節豐富。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>Kubernetes 升級風險：minor version 升級的 breaking change</li>
<li>升級回滾困境：為何 k8s control plane 不能直接降版</li>
<li>大規模 stateful workload 的特殊性：pod 重排對狀態服務的衝擊</li>
<li>內部 IR 流程：Reddit 的 IR commander / scribe 結構公開度</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2023-03</td>
          <td>Pi Day k8s 升級 314 分鐘</td>
          <td>k8s upgrade、control plane 回滾困境</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Reddit 這個案例在講的是 Kubernetes 升級如何在大規模 stateful 工作負載上拉長事故。讀者先看懂控制平面升級、回滾限制與狀態服務的特性，再把 Pi Day outage 當成升級風險的具體樣本。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 control plane 進行升級時，最先要保住的是回滾空間與資料完整性。當 pod 重排碰到 stateful workload 時，恢復節奏就不能只看節點健康，而要看整個狀態層是否真的穩回來。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否判斷問題是在 k8s 升級還是 workload 本身</li>
<li>能否把回滾限制與控制平面風險講清楚</li>
<li>能否辨識 stateful workload 的額外恢復成本</li>
<li>能否把 IR commander / scribe 的流程用在對外說明</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Reddit 和 GitHub、Heroku 的交集在於，它們都會把平台層變更直接反映成使用者可見的 outage。這頁最值得和 GCP 一起看，因為 Kubernetes 升級與 control plane 回滾問題，能很好地補足「服務自己沒有寫錯，但平台還是會出事」這個視角。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2023-03 Pi Day 314 分鐘事故是 k8s 升級與 stateful workload 互相放大的樣本。</li>
<li>這類事件特別能看出 control plane 回滾為何比一般服務回滾更麻煩。</li>
<li>IR commander / scribe 讓對外資訊流有固定節奏。</li>
<li>k8s 升級風險和其他平台事故頁可以互相對照。</li>
<li>stateful workload 的 pod 重排會把效能恢復拉長。</li>
<li>control plane rollback 的限制讓升級決策必須更早做完。</li>
<li>kube upgrade 是整個平台控制面的變更，用版本更新的心態處理會低估風險。</li>
<li>stateful service 的 cold start 會把恢復時間拉長到使用者可感知的程度。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">RD1</a></td>
          <td>Kubernetes 升級事故</td>
          <td>將平台升級變更納入事故分級與回退節奏</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.redditstatus.com/">Reddit Status</a>：Reddit 狀態頁與 incident history。</li>
<li><a href="https://www.redditstatus.com/history">Reddit Status - Incident History</a>：歷史事故與 uptime 檢視。</li>
<li><a href="https://www.redditstatus.com/api">Reddit Status - API</a>：status page API 文件。</li>
<li><a href="https://redditinc.com/blog/the-search-for-better-search-at-reddit">The Search for Better Search at Reddit</a>：Reddit 工程內容總入口之一，補基礎工程脈絡。</li>
</ul>
]]></content:encoded></item><item><title>3.C23 Bloomberg：多租戶 vhost + 自助平台化</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/</guid><description>&lt;p>Bloomberg 的 RabbitMQ 平台化案例揭露了 broker 從幾個團隊的工具演變成上百個團隊的共享基礎設施時，治理責任邊界應該前置設計，而非在規模化之後補救。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Bloomberg 有 5000+ 工程師，內部系統涵蓋金融資料處理、交易系統、新聞分發與分析平台。RabbitMQ 的使用從最初幾個團隊的 microservice 解耦開始，逐步擴展到上百個團隊。到 2019 年，Bloomberg 的 RabbitMQ 基礎設施每週處理超過 2 億條訊息，尖峰每秒數萬條。&lt;/p>
&lt;p>這個規模下，原本由平台團隊手動配置的 queue / exchange / binding 模式無法持續 — 上百個團隊各自有不同的 queue 需求，平台團隊成為所有變更的人工瓶頸。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="多租戶隔離">多租戶隔離&lt;/h3>
&lt;p>多個團隊共用同一個 RabbitMQ cluster 時，一個團隊的 queue 爆量或 consumer 故障可能影響其他團隊的訊息處理。RabbitMQ 的 Erlang scheduler 是共用的 — 一個 queue 的 message accumulation 會消耗 broker 的記憶體跟 CPU，影響同 cluster 上所有 queue 的效能。&lt;/p>
&lt;p>隔離需要在 broker 層實作，client 端的 best practice（限制 message size、設定 TTL）只能降低風險但無法保證隔離。&lt;/p>
&lt;h3 id="自助配置的安全邊界">自助配置的安全邊界&lt;/h3>
&lt;p>讓上百個團隊自助建立 queue / exchange / binding 需要明確的安全邊界 — 團隊 A 能在自己的 namespace 建立資源，但不能存取團隊 B 的 queue。RabbitMQ 的 vhost 機制提供了這個隔離單位，但 vhost 的建立跟權限配置本身需要自動化。&lt;/p>
&lt;h3 id="容量規劃與配額">容量規劃與配額&lt;/h3>
&lt;p>共享 cluster 的容量被所有租戶分攤。沒有配額機制時，一個團隊的 queue 可以無限增長直到 broker 記憶體告警、觸發 flow control、影響全部租戶。配額需要在 queue 層面設定上限（max-length、max-length-bytes），同時提供超出配額時的降級策略而非直接拒絕。&lt;/p>
&lt;h2 id="解法vhost-分層--自助平台">解法：vhost 分層 + 自助平台&lt;/h2>
&lt;h3 id="vhost-作為租戶邊界">Vhost 作為租戶邊界&lt;/h3>
&lt;p>Bloomberg 把 vhost 作為多租戶隔離的基本單位。每個團隊（或每個應用）分配一個 vhost，vhost 內的 queue / exchange / binding 只對該團隊可見。跨 vhost 的訊息傳遞透過 shovel 或 federation plugin，需要顯式配置，預設不互通。&lt;/p>
&lt;p>Vhost 的隔離粒度是「資源可見性 + 權限」而非「硬體資源」。同 cluster 上的 vhost 仍然共用 Erlang runtime 跟記憶體。完全的硬體隔離需要獨立 cluster — Bloomberg 對高敏感度的工作負載（交易相關）使用專用 cluster，一般業務共用大 cluster + vhost 隔離。&lt;/p>
&lt;h3 id="自助-vhost-註冊">自助 vhost 註冊&lt;/h3>
&lt;p>Bloomberg 建立了內部自助平台，團隊透過 API 或內部 portal 申請 vhost。申請時需要提供：應用名稱、預期的 message rate、保留期限、是否需要 HA（mirrored / quorum queue）。平台自動建立 vhost、設定權限、分配連線端點。&lt;/p>
&lt;p>自助流程的價值是去除平台團隊的人工瓶頸。新團隊從申請到拿到可用的 RabbitMQ 端點，時間從「提 ticket 等平台團隊排程」縮短到「填表 → 自動配置 → 立即可用」。&lt;/p>
&lt;h3 id="配額與監控">配額與監控&lt;/h3>
&lt;p>每個 vhost 有預設配額（max-length、max-connections）。超出配額時 broker 行為可配 — drop-head（丟最舊的訊息）或 reject-publish（拒絕新訊息）。配額不是懲罰機制，是保護共享 cluster 的防線。&lt;/p>
&lt;p>監控用 RabbitMQ 的 management plugin + Prometheus exporter，按 vhost 維度匯出 queue depth、message rate、connection count。每個 vhost 的 dashboard 對應到 owner 團隊，讓團隊自行判讀自己的使用狀況。&lt;/p></description><content:encoded><![CDATA[<p>Bloomberg 的 RabbitMQ 平台化案例揭露了 broker 從幾個團隊的工具演變成上百個團隊的共享基礎設施時，治理責任邊界應該前置設計，而非在規模化之後補救。</p>
<h2 id="業務背景">業務背景</h2>
<p>Bloomberg 有 5000+ 工程師，內部系統涵蓋金融資料處理、交易系統、新聞分發與分析平台。RabbitMQ 的使用從最初幾個團隊的 microservice 解耦開始，逐步擴展到上百個團隊。到 2019 年，Bloomberg 的 RabbitMQ 基礎設施每週處理超過 2 億條訊息，尖峰每秒數萬條。</p>
<p>這個規模下，原本由平台團隊手動配置的 queue / exchange / binding 模式無法持續 — 上百個團隊各自有不同的 queue 需求，平台團隊成為所有變更的人工瓶頸。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="多租戶隔離">多租戶隔離</h3>
<p>多個團隊共用同一個 RabbitMQ cluster 時，一個團隊的 queue 爆量或 consumer 故障可能影響其他團隊的訊息處理。RabbitMQ 的 Erlang scheduler 是共用的 — 一個 queue 的 message accumulation 會消耗 broker 的記憶體跟 CPU，影響同 cluster 上所有 queue 的效能。</p>
<p>隔離需要在 broker 層實作，client 端的 best practice（限制 message size、設定 TTL）只能降低風險但無法保證隔離。</p>
<h3 id="自助配置的安全邊界">自助配置的安全邊界</h3>
<p>讓上百個團隊自助建立 queue / exchange / binding 需要明確的安全邊界 — 團隊 A 能在自己的 namespace 建立資源，但不能存取團隊 B 的 queue。RabbitMQ 的 vhost 機制提供了這個隔離單位，但 vhost 的建立跟權限配置本身需要自動化。</p>
<h3 id="容量規劃與配額">容量規劃與配額</h3>
<p>共享 cluster 的容量被所有租戶分攤。沒有配額機制時，一個團隊的 queue 可以無限增長直到 broker 記憶體告警、觸發 flow control、影響全部租戶。配額需要在 queue 層面設定上限（max-length、max-length-bytes），同時提供超出配額時的降級策略而非直接拒絕。</p>
<h2 id="解法vhost-分層--自助平台">解法：vhost 分層 + 自助平台</h2>
<h3 id="vhost-作為租戶邊界">Vhost 作為租戶邊界</h3>
<p>Bloomberg 把 vhost 作為多租戶隔離的基本單位。每個團隊（或每個應用）分配一個 vhost，vhost 內的 queue / exchange / binding 只對該團隊可見。跨 vhost 的訊息傳遞透過 shovel 或 federation plugin，需要顯式配置，預設不互通。</p>
<p>Vhost 的隔離粒度是「資源可見性 + 權限」而非「硬體資源」。同 cluster 上的 vhost 仍然共用 Erlang runtime 跟記憶體。完全的硬體隔離需要獨立 cluster — Bloomberg 對高敏感度的工作負載（交易相關）使用專用 cluster，一般業務共用大 cluster + vhost 隔離。</p>
<h3 id="自助-vhost-註冊">自助 vhost 註冊</h3>
<p>Bloomberg 建立了內部自助平台，團隊透過 API 或內部 portal 申請 vhost。申請時需要提供：應用名稱、預期的 message rate、保留期限、是否需要 HA（mirrored / quorum queue）。平台自動建立 vhost、設定權限、分配連線端點。</p>
<p>自助流程的價值是去除平台團隊的人工瓶頸。新團隊從申請到拿到可用的 RabbitMQ 端點，時間從「提 ticket 等平台團隊排程」縮短到「填表 → 自動配置 → 立即可用」。</p>
<h3 id="配額與監控">配額與監控</h3>
<p>每個 vhost 有預設配額（max-length、max-connections）。超出配額時 broker 行為可配 — drop-head（丟最舊的訊息）或 reject-publish（拒絕新訊息）。配額不是懲罰機制，是保護共享 cluster 的防線。</p>
<p>監控用 RabbitMQ 的 management plugin + Prometheus exporter，按 vhost 維度匯出 queue depth、message rate、connection count。每個 vhost 的 dashboard 對應到 owner 團隊，讓團隊自行判讀自己的使用狀況。</p>
<h2 id="取捨">取捨</h2>
<p><strong>Vhost 隔離 vs 硬體隔離</strong>：vhost 隔離成本低（不需要額外 cluster），但隔離程度有限 — Erlang scheduler 跟記憶體仍然共用。Bloomberg 的做法是多數團隊用 vhost 隔離、高敏感度工作負載用專用 cluster，兩者共存。</p>
<p><strong>自助配置 vs 中央管控</strong>：自助配置加速團隊迭代，但也增加了 configuration drift 的風險。Bloomberg 透過配額跟自動化審計（定期掃描 vhost 的 queue 狀態、alert 異常 pattern）平衡自助跟管控。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 的多租戶治理責任</li>
<li><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6 Uber Kafka 平台</a>：Kafka 生態的多租戶治理比較 — Kafka 用 topic-level ACL + quota，RabbitMQ 用 vhost</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：平台團隊跟服務團隊的 ownership 邊界</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>RabbitMQ 的使用團隊數從個位數增長到雙位數、平台團隊成為配置瓶頸</li>
<li>單一 cluster 上的 queue 數量超過數百個、owner 不明</li>
<li>某個團隊的 queue 爆量影響了其他團隊的 consumer 效能</li>
<li>新團隊要用 RabbitMQ 但平台團隊的 ticket 要排隊數天</li>
<li>沒有 per-team 的 message rate 或 queue depth 監控</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cloudamqp.com/blog/growing-a-farm-of-rabbits.html">Growing a Farm of Rabbits at Bloomberg</a></li>
</ul>
]]></content:encoded></item><item><title>9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 +75%、成本 -28%</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/</guid><description>&lt;p>這個案例的核心責任是說明 Netflix 在 AWS 上的「資料庫統一」決策、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS 多集群&lt;/a> 形成對照。Riot 走「single-tenant per workload、246 個 cluster」、Netflix 走「跨 application 統一 Aurora、減少 DB 種類」 — 兩條路徑都是大規模平台的 &lt;em>合理&lt;/em> 選擇、但工程哲學完全不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Netflix 在 Aurora 整合的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora&lt;/a>）：&lt;/p>
&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>up to 75%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本下降&lt;/td>
 &lt;td>28%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>月串流時數&lt;/td>
 &lt;td>billions of hours&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>global&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>整合範圍&lt;/td>
 &lt;td>多套 relational DB → Aurora&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務架構&lt;/td>
 &lt;td>全球分散式 microservices&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容器編排&lt;/td>
 &lt;td>Amazon EKS&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Netflix 整體 AWS 使用：「Netflix uses AWS to deliver billions of hours of content monthly and runs its analytics platform for optimum performance of its global service. AWS enables Netflix to quickly deploy thousands of servers and terabytes of storage within minutes.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Netflix Aurora 整合揭露三個大規模平台 DB 治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「DB 種類太多」本身是規模化的成本&lt;/strong>：Netflix 過往用 PostgreSQL、MySQL、Oracle 等不同 RDB、每個都需要不同 DBA 知識、不同備份、不同 monitoring 流程。整合到 Aurora 不只是「換 DB」、是「降低運維 surface area」、釋放工程資源。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom&lt;/a> 同類訴求。&lt;/li>
&lt;li>&lt;strong>75% performance improvement 是 Aurora storage layer 的本質優勢&lt;/strong>：Aurora 把 storage 跟 compute 分離、storage 用分散式 log-based 設計、replication 在 storage 層處理、不在 compute 層 — 這讓 read replica 不會受 master 寫入壓力影響、性能曲線比傳統 RDB 平滑。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的儲存層 vs 計算層分離。&lt;/li>
&lt;li>&lt;strong>Netflix 的 DB 工作負載大多是「微服務私有 store」&lt;/strong>：Netflix 微服務各自有自己的 Aurora cluster、不共用 — 跟 monolith 「一個大 DB 撐全部」相反。這層架構讓「DB 容量規劃」變成「每個微服務的容量規劃」、複雜度分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 service decomposition、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&amp;#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&amp;#43; 個微服務承載 8 倍峰值流量、跨 200&amp;#43; 城市">9.C7 Lyft 微服務&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Netflix 在 AWS 上的「資料庫統一」決策、跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS 多集群</a> 形成對照。Riot 走「single-tenant per workload、246 個 cluster」、Netflix 走「跨 application 統一 Aurora、減少 DB 種類」 — 兩條路徑都是大規模平台的 <em>合理</em> 選擇、但工程哲學完全不同。</p>
<h2 id="觀察">觀察</h2>
<p>Netflix 在 Aurora 整合的關鍵敘述（引自 <a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>效能提升</td>
          <td>up to 75%</td>
      </tr>
      <tr>
          <td>成本下降</td>
          <td>28%</td>
      </tr>
      <tr>
          <td>月串流時數</td>
          <td>billions of hours</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>global</td>
      </tr>
      <tr>
          <td>整合範圍</td>
          <td>多套 relational DB → Aurora</td>
      </tr>
      <tr>
          <td>微服務架構</td>
          <td>全球分散式 microservices</td>
      </tr>
      <tr>
          <td>容器編排</td>
          <td>Amazon EKS</td>
      </tr>
  </tbody>
</table>
<p>Netflix 整體 AWS 使用：「Netflix uses AWS to deliver billions of hours of content monthly and runs its analytics platform for optimum performance of its global service. AWS enables Netflix to quickly deploy thousands of servers and terabytes of storage within minutes.」</p>
<h2 id="判讀">判讀</h2>
<p>Netflix Aurora 整合揭露三個大規模平台 DB 治理重點。</p>
<ol>
<li><strong>「DB 種類太多」本身是規模化的成本</strong>：Netflix 過往用 PostgreSQL、MySQL、Oracle 等不同 RDB、每個都需要不同 DBA 知識、不同備份、不同 monitoring 流程。整合到 Aurora 不只是「換 DB」、是「降低運維 surface area」、釋放工程資源。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化、跟 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> 同類訴求。</li>
<li><strong>75% performance improvement 是 Aurora storage layer 的本質優勢</strong>：Aurora 把 storage 跟 compute 分離、storage 用分散式 log-based 設計、replication 在 storage 層處理、不在 compute 層 — 這讓 read replica 不會受 master 寫入壓力影響、性能曲線比傳統 RDB 平滑。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的儲存層 vs 計算層分離。</li>
<li><strong>Netflix 的 DB 工作負載大多是「微服務私有 store」</strong>：Netflix 微服務各自有自己的 Aurora cluster、不共用 — 跟 monolith 「一個大 DB 撐全部」相反。這層架構讓「DB 容量規劃」變成「每個微服務的容量規劃」、複雜度分散。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition、跟 <a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft 微服務</a>。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「effective 75% improvement」是 <em>跨多個 workload 的最大改善幅度</em>、不是「每個 workload 都 +75%」。實際每個 workload 改善幅度從 10% 到 75% 不等。</li>
<li>Netflix 數據層遠不止 Aurora — 還有 Cassandra（playback metadata）、EVCache（cache layer）、Iceberg（data warehouse）。Aurora 主要是「需要 ACID 的 OLTP 工作負載」、不是「all-purpose store」。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>DB 種類整合是規模化的必要工程</strong>：每多一種 DB 就多一套運維 surface。在能合理 consolidate 的時候整合、降低 ops 複雜度。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor diversity 取捨。</li>
<li><strong>storage / compute 分離是 OLTP 擴容的關鍵</strong>：Aurora、Spanner、TiDB 都採類似設計、是現代 cloud DB 的共同特徵。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的 storage layer 設計。</li>
<li><strong>微服務私有 store 比共用 DB 容量規劃簡單</strong>：每個服務各自管 DB 容量、跨服務 contention 變成 <em>network 議題</em> 而非 <em>DB lock 議題</em>。</li>
<li><strong>大規模平台必須區分「OLTP 用 Aurora」「analytics 用 data lake」「KV 用 DynamoDB」「cache 用 EVCache」</strong>：Netflix 用各種 DB、不是一招打天下。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence。</li>
</ol>
<p>跨平台等效：GCP Spanner（替代 OLTP）+ Bigtable（替代 KV）+ BigQuery（替代 analytics）；Azure Cosmos DB（替代多 model）+ SQL Hyperscale + Synapse — 各雲商提供類似 stack。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模平台 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS</a>（不同 consolidation 策略）</li>
<li>想理解 Aurora 設計 → <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想做 polyglot persistence 選型 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>想做 DB consolidation 規劃 → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解 +75% 的 storage / compute 解耦根因 → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a></li>
<li>想規劃自管 PostgreSQL / MySQL 遷入 Aurora 的步驟 → <a href="/blog/backend/01-database/vendors/aurora/migrate-from-self-managed-pg-mysql/" data-link-title="從自管 PostgreSQL / MySQL 遷到 Aurora：operational redesign migration playbook" data-link-desc="PostgreSQL / MySQL → Aurora 的 Type C operational redesign hybrid playbook、6 規格面（Driver / Diff audit / Phase plan / Evidence / Cutover / Cleanup）、Standard Chartered 合規 lead time 模型、Netflix 非 all-purpose store 邊界">從自管 PostgreSQL/MySQL 遷入 Aurora</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora, achieving up to 75% improved performance</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/innovators/netflix/">Netflix on AWS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/netflix-case-study/">Netflix Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>Netflix：FIT 證據交接與 Release Gate 回寫</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/</guid><description>&lt;p>FIT（Failure Injection Testing）的核心責任是產生可決策的證據，故障演示只是過程。當實驗結果無法直接回答「能不能放行」，FIT 就只是測試活動，不是可靠性控制面。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>團隊常在故障注入後留下 dashboard 截圖與結論摘要，但 release decision 仍靠主觀討論。這種斷裂會讓同類風險反覆出現，因為每次都在重新辯論，而不是沿用同一套 evidence 欄位。&lt;/p>
&lt;h2 id="決策機制">決策機制&lt;/h2>
&lt;p>要讓 FIT 成為 release gate 輸入，必須把實驗輸出結構化成決策欄位。&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>steady-state impact&lt;/td>
 &lt;td>注入後是否仍維持服務承諾&lt;/td>
 &lt;td>判斷能否繼續 rollout&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>abort trigger record&lt;/td>
 &lt;td>停止條件是否被觸發、何時觸發&lt;/td>
 &lt;td>判斷是否進入凍結與回退&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>fallback result&lt;/td>
 &lt;td>降級路徑是否可用、恢復是否收斂&lt;/td>
 &lt;td>判斷事故時能否安全止血&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>dependency drift&lt;/td>
 &lt;td>受影響依賴是否落在預期範圍&lt;/td>
 &lt;td>判斷 blast radius 是否可接受&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>verification evidence&lt;/td>
 &lt;td>證據是否足以支持 release&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rule rollout anomaly&lt;/td>
 &lt;td>規則推送後是否偏離預期&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>incident decision lag&lt;/td>
 &lt;td>事故時是否可快速調用證據&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>evidence write-back&lt;/td>
 &lt;td>教訓是否回寫成下次驗證輸入&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>最常見錯誤是把 FIT 報告寫成敘事文件，沒有決策欄位，導致放行時無法直接引用。另一個錯誤是只記錄成功路徑，忽略 abort trigger 與 fallback 失敗，讓風險被低估。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先把 FIT 輸出整理到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff&lt;/a>，再接到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate&lt;/a> 做放行判斷。事故發生時由 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a> 快速提取決策證據，最後回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://netflixtechblog.com/fit-failure-injection-testing-35d8e2a9bb2e">FIT: Failure Injection Testing&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>FIT（Failure Injection Testing）的核心責任是產生可決策的證據，故障演示只是過程。當實驗結果無法直接回答「能不能放行」，FIT 就只是測試活動，不是可靠性控制面。</p>
<h2 id="問題場景">問題場景</h2>
<p>團隊常在故障注入後留下 dashboard 截圖與結論摘要，但 release decision 仍靠主觀討論。這種斷裂會讓同類風險反覆出現，因為每次都在重新辯論，而不是沿用同一套 evidence 欄位。</p>
<h2 id="決策機制">決策機制</h2>
<p>要讓 FIT 成為 release gate 輸入，必須把實驗輸出結構化成決策欄位。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>核心問題</th>
          <th>決策用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>steady-state impact</td>
          <td>注入後是否仍維持服務承諾</td>
          <td>判斷能否繼續 rollout</td>
      </tr>
      <tr>
          <td>abort trigger record</td>
          <td>停止條件是否被觸發、何時觸發</td>
          <td>判斷是否進入凍結與回退</td>
      </tr>
      <tr>
          <td>fallback result</td>
          <td>降級路徑是否可用、恢復是否收斂</td>
          <td>判斷事故時能否安全止血</td>
      </tr>
      <tr>
          <td>dependency drift</td>
          <td>受影響依賴是否落在預期範圍</td>
          <td>判斷 blast radius 是否可接受</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>verification evidence</td>
          <td>證據是否足以支持 release</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a></td>
      </tr>
      <tr>
          <td>rule rollout anomaly</td>
          <td>規則推送後是否偏離預期</td>
          <td><a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24</a></td>
      </tr>
      <tr>
          <td>incident decision lag</td>
          <td>事故時是否可快速調用證據</td>
          <td><a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a></td>
      </tr>
      <tr>
          <td>evidence write-back</td>
          <td>教訓是否回寫成下次驗證輸入</td>
          <td><a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>最常見錯誤是把 FIT 報告寫成敘事文件，沒有決策欄位，導致放行時無法直接引用。另一個錯誤是只記錄成功路徑，忽略 abort trigger 與 fallback 失敗，讓風險被低估。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先把 FIT 輸出整理到 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff</a>，再接到 <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 Rule Rollout Safety Gate</a> 做放行判斷。事故發生時由 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a> 快速提取決策證據，最後回寫 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://netflixtechblog.com/fit-failure-injection-testing-35d8e2a9bb2e">FIT: Failure Injection Testing</a></li>
<li><a href="https://github.com/Netflix/chaosmonkey">Netflix/chaosmonkey</a></li>
</ul>
]]></content:encoded></item><item><title>Meta / Facebook</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/</guid><description>&lt;p>Meta（前 Facebook）是超大規模分散式系統的代表、2021-10 BGP 全球失效事故是大規模事故敘事的教學標竿。Engineering blog 公開的 reliability 文章涵蓋 region failover、cell architecture 等深度實踐。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>BGP 與 DNS 自我封鎖：2021-10 事故揭露的內部依賴鎖死&lt;/li>
&lt;li>Region Failover：超大規模服務的跨區切換挑戰&lt;/li>
&lt;li>Cell Architecture：Facebook 規模下的 cell 設計&lt;/li>
&lt;li>Storm：Internal incident management 系統公開的設計&lt;/li>
&lt;/ul>
&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>2021-10 BGP 事故&lt;/td>
 &lt;td>配置變更鎖死自己、recovery 工具失效&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Region Failover&lt;/td>
 &lt;td>超大規模 traffic shift 的設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Storm IM System&lt;/td>
 &lt;td>內部 IR 工具的揭露&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Reliability Reviews&lt;/td>
 &lt;td>服務級可靠性審查制度&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Meta 這個案例在講的是超大規模系統如何面對全球級網路與控制面事故。讀者先抓 BGP、自我封鎖、region failover 與 MySQL Raft 這些原語，再把它們當成超大規模恢復能力的組件。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當外部路由或內部配置互相牽制時，事故會把恢復工具一起拖進失效狀態。當服務開始做更快的 failover 投資時，真正要看的是它是否能縮短恢復時間並降低手動介入成本，單點工具層面的評估遠遠不夠。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否分辨事故是路由、配置還是服務層問題&lt;/li>
&lt;li>能否說明 region failover 的前置條件&lt;/li>
&lt;li>能否把 IR 工具與對外說明串成一致時間線&lt;/li>
&lt;li>能否把資料庫 failover 投資對應到恢復時間縮短&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Meta 的價值在於把超大規模網路事故和恢復工具放在一起看，這和 AWS S3、GCP、Cloudflare 都是在談「控制面出事時會擴散多遠」。如果先讀 Meta，再回看其他案例，會更容易看出 region failover 和 route propagation 的真正成本。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>2021-10 BGP 事故顯示一個控制面變更可以讓整個公司失去對外可見性。&lt;/li>
&lt;li>MySQL Raft 代表的是把資料庫 failover 工具化，縮短人工介入時間。&lt;/li>
&lt;li>region failover 顯示超大規模 traffic shift 的成本。&lt;/li>
&lt;li>reliability reviews 讓服務級風險在變更前先被看見。&lt;/li>
&lt;li>cell architecture 讓大規模服務把故障切成可管理的單位。&lt;/li>
&lt;li>Storm 代表內部 incident management 工具如何支撐跨團隊協作。&lt;/li>
&lt;li>DNS 自我封鎖讓內外部控制面一起失效。&lt;/li>
&lt;li>traffic shift 讓恢復不只是切流量，而是管理整個依賴網。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">M1&lt;/a>&lt;/td>
 &lt;td>Region Failover 邊界治理&lt;/td>
 &lt;td>把跨區故障擴散限制在可回復邊界內&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">M2&lt;/a>&lt;/td>
 &lt;td>BGP 事故與控制面恢復順序&lt;/td>
 &lt;td>恢復工具依賴已故障系統時的恢復順序與 out-of-band 設計&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/">More details about the October 4 outage&lt;/a>：Meta 2021-10 outage 的技術回顧。&lt;/li>
&lt;li>&lt;a href="https://engineering.fb.com/2021/10/04/networking-traffic/outage/">Update about the October 4th outage&lt;/a>：事故初始公開說明。&lt;/li>
&lt;li>&lt;a href="https://engineering.fb.com/2023/05/16/data-infrastructure/mysql-raft-meta/">Building and deploying MySQL Raft at Meta&lt;/a>：更快 failover 與可靠性投資。&lt;/li>
&lt;li>&lt;a href="https://engineering.fb.com/2014/06/05/core-infra/hydrabase-the-evolution-of-hbase-facebook/">HydraBase – The evolution of HBase@Facebook&lt;/a>：分散式儲存與 failover 的早期實踐。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Meta（前 Facebook）是超大規模分散式系統的代表、2021-10 BGP 全球失效事故是大規模事故敘事的教學標竿。Engineering blog 公開的 reliability 文章涵蓋 region failover、cell architecture 等深度實踐。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>BGP 與 DNS 自我封鎖：2021-10 事故揭露的內部依賴鎖死</li>
<li>Region Failover：超大規模服務的跨區切換挑戰</li>
<li>Cell Architecture：Facebook 規模下的 cell 設計</li>
<li>Storm：Internal incident management 系統公開的設計</li>
</ul>
<h2 id="預計收錄實踐">預計收錄實踐</h2>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2021-10 BGP 事故</td>
          <td>配置變更鎖死自己、recovery 工具失效</td>
      </tr>
      <tr>
          <td>Region Failover</td>
          <td>超大規模 traffic shift 的設計</td>
      </tr>
      <tr>
          <td>Storm IM System</td>
          <td>內部 IR 工具的揭露</td>
      </tr>
      <tr>
          <td>Reliability Reviews</td>
          <td>服務級可靠性審查制度</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Meta 這個案例在講的是超大規模系統如何面對全球級網路與控制面事故。讀者先抓 BGP、自我封鎖、region failover 與 MySQL Raft 這些原語，再把它們當成超大規模恢復能力的組件。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當外部路由或內部配置互相牽制時，事故會把恢復工具一起拖進失效狀態。當服務開始做更快的 failover 投資時，真正要看的是它是否能縮短恢復時間並降低手動介入成本，單點工具層面的評估遠遠不夠。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否分辨事故是路由、配置還是服務層問題</li>
<li>能否說明 region failover 的前置條件</li>
<li>能否把 IR 工具與對外說明串成一致時間線</li>
<li>能否把資料庫 failover 投資對應到恢復時間縮短</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Meta 的價值在於把超大規模網路事故和恢復工具放在一起看，這和 AWS S3、GCP、Cloudflare 都是在談「控制面出事時會擴散多遠」。如果先讀 Meta，再回看其他案例，會更容易看出 region failover 和 route propagation 的真正成本。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>2021-10 BGP 事故顯示一個控制面變更可以讓整個公司失去對外可見性。</li>
<li>MySQL Raft 代表的是把資料庫 failover 工具化，縮短人工介入時間。</li>
<li>region failover 顯示超大規模 traffic shift 的成本。</li>
<li>reliability reviews 讓服務級風險在變更前先被看見。</li>
<li>cell architecture 讓大規模服務把故障切成可管理的單位。</li>
<li>Storm 代表內部 incident management 工具如何支撐跨團隊協作。</li>
<li>DNS 自我封鎖讓內外部控制面一起失效。</li>
<li>traffic shift 讓恢復不只是切流量，而是管理整個依賴網。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">M1</a></td>
          <td>Region Failover 邊界治理</td>
          <td>把跨區故障擴散限制在可回復邊界內</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">M2</a></td>
          <td>BGP 事故與控制面恢復順序</td>
          <td>恢復工具依賴已故障系統時的恢復順序與 out-of-band 設計</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/">More details about the October 4 outage</a>：Meta 2021-10 outage 的技術回顧。</li>
<li><a href="https://engineering.fb.com/2021/10/04/networking-traffic/outage/">Update about the October 4th outage</a>：事故初始公開說明。</li>
<li><a href="https://engineering.fb.com/2023/05/16/data-infrastructure/mysql-raft-meta/">Building and deploying MySQL Raft at Meta</a>：更快 failover 與可靠性投資。</li>
<li><a href="https://engineering.fb.com/2014/06/05/core-infra/hydrabase-the-evolution-of-hbase-facebook/">HydraBase – The evolution of HBase@Facebook</a>：分散式儲存與 failover 的早期實踐。</li>
</ul>
]]></content:encoded></item><item><title>Microsoft 365</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/</guid><description>&lt;p>Microsoft 365（Exchange Online / Teams / SharePoint）是企業 SaaS 套件的代表、事故影響企業生產力、Microsoft 的 PIR 揭露格式具有教學價值。&lt;/p>
&lt;h2 id="規劃重點">規劃重點&lt;/h2>
&lt;ul>
&lt;li>企業 SaaS 套件的 blast radius：跨產品事故對企業客戶的影響&lt;/li>
&lt;li>跟 Azure AD 的依賴：Identity 失效 vs M365 服務失效的分層&lt;/li>
&lt;li>Tenant-level vs region-level 影響：多租戶 SaaS 的部分事故揭露&lt;/li>
&lt;li>PIR 格式：Microsoft 的 Public Incident Report 結構&lt;/li>
&lt;/ul>
&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>2023&lt;/td>
 &lt;td>Exchange Online 大規模失效&lt;/td>
 &lt;td>跨企業客戶通訊影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2024&lt;/td>
 &lt;td>Teams 全球失效&lt;/td>
 &lt;td>同步通訊工具失效的 IR 通訊困境&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例定位">案例定位&lt;/h2>
&lt;p>Microsoft 365 這個案例在講的是一組共享 productivity 服務如何把單點事故變成廣域通訊問題。讀者先看懂 service health、PIR 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> 的責任，再把 M365 視為企業客戶的協作底層。&lt;/p>
&lt;h2 id="判讀重點">判讀重點&lt;/h2>
&lt;p>當 Exchange Online 或 Teams 失效時，復原不只是在服務本身恢復，還要讓客戶知道通訊與協作功能何時能回來。這類事故的關鍵在於可見性與一致的對外更新，讓企業能決定是否切換替代流程。&lt;/p>
&lt;h2 id="可操作判準">可操作判準&lt;/h2>
&lt;ul>
&lt;li>能否快速判斷影響的是哪個 M365 子服務&lt;/li>
&lt;li>能否從 service health 看出恢復順序&lt;/li>
&lt;li>能否把 PIR 的資訊轉成客戶能執行的替代路徑&lt;/li>
&lt;li>能否把 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> 與實際 outage 對齊&lt;/li>
&lt;/ul>
&lt;h2 id="與其他案例的關係">與其他案例的關係&lt;/h2>
&lt;p>Microsoft 365 和 Azure AD 是一組必讀對照，前者看協作服務層的影響，後者看 identity 基礎層的失效。它也能和 Slack 一起讀，因為兩者都在說明當通訊平台出事時，客戶需要的是清楚的狀態與替代流程，而不是只有技術術語。&lt;/p>
&lt;h2 id="代表樣本">代表樣本&lt;/h2>
&lt;ul>
&lt;li>Exchange Online 大規模失效代表企業通訊與協作服務的廣域影響。&lt;/li>
&lt;li>Teams 全球失效則顯示 IR 通訊本身也會受到通訊工具失效的影響。&lt;/li>
&lt;li>service health 與 PIR 的公開格式會影響客戶判讀速度。&lt;/li>
&lt;li>tenant-level 與 region-level 失效要分開看。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> 讓 Microsoft 能把復原流程標準化。&lt;/li>
&lt;li>built-in service resiliency 是企業 SaaS 的預設期待。&lt;/li>
&lt;li>shared productivity suite 讓一個服務失效就能放大成企業生產力問題。&lt;/li>
&lt;li>customer communication 與技術復原並行，才能避免恢復過程的資訊落差。&lt;/li>
&lt;/ul>
&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;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/" data-link-title="Microsoft 365：套件級身分驗證事故" data-link-desc="企業套件在身份依賴失效時，如何同步處理跨產品影響與對外揭露。">M365-1&lt;/a>&lt;/td>
 &lt;td>套件級身份事故&lt;/td>
 &lt;td>將跨產品影響分層並同步對外通訊與回復順序&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-platform-service-description/service-health-and-continuity?country=au&amp;amp;culture=en-au">Service health and continuity&lt;/a>：M365 服務健康、PIR 與通訊政策。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/microsoft-365/enterprise/view-service-health?view=o365-worldwide">How to check Microsoft 365 service health&lt;/a>：Service health 的使用方式。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/services-hub/unified/health/ir-m365">Microsoft 365 incident readiness - Unified&lt;/a>：Microsoft 的 incident readiness / PIR 流程。&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/en-us/compliance/assurance/assurance-m365-service-resiliency?source=recommendations">Built-in service resiliency in Microsoft 365&lt;/a>：M365 服務韌性與 downtime 定義。&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Microsoft 365（Exchange Online / Teams / SharePoint）是企業 SaaS 套件的代表、事故影響企業生產力、Microsoft 的 PIR 揭露格式具有教學價值。</p>
<h2 id="規劃重點">規劃重點</h2>
<ul>
<li>企業 SaaS 套件的 blast radius：跨產品事故對企業客戶的影響</li>
<li>跟 Azure AD 的依賴：Identity 失效 vs M365 服務失效的分層</li>
<li>Tenant-level vs region-level 影響：多租戶 SaaS 的部分事故揭露</li>
<li>PIR 格式：Microsoft 的 Public Incident Report 結構</li>
</ul>
<h2 id="預計收錄事故">預計收錄事故</h2>
<table>
  <thead>
      <tr>
          <th>年份</th>
          <th>事故</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2023</td>
          <td>Exchange Online 大規模失效</td>
          <td>跨企業客戶通訊影響</td>
      </tr>
      <tr>
          <td>2024</td>
          <td>Teams 全球失效</td>
          <td>同步通訊工具失效的 IR 通訊困境</td>
      </tr>
  </tbody>
</table>
<h2 id="案例定位">案例定位</h2>
<p>Microsoft 365 這個案例在講的是一組共享 productivity 服務如何把單點事故變成廣域通訊問題。讀者先看懂 service health、PIR 與 <a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> 的責任，再把 M365 視為企業客戶的協作底層。</p>
<h2 id="判讀重點">判讀重點</h2>
<p>當 Exchange Online 或 Teams 失效時，復原不只是在服務本身恢復，還要讓客戶知道通訊與協作功能何時能回來。這類事故的關鍵在於可見性與一致的對外更新，讓企業能決定是否切換替代流程。</p>
<h2 id="可操作判準">可操作判準</h2>
<ul>
<li>能否快速判斷影響的是哪個 M365 子服務</li>
<li>能否從 service health 看出恢復順序</li>
<li>能否把 PIR 的資訊轉成客戶能執行的替代路徑</li>
<li>能否把 <a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> 與實際 outage 對齊</li>
</ul>
<h2 id="與其他案例的關係">與其他案例的關係</h2>
<p>Microsoft 365 和 Azure AD 是一組必讀對照，前者看協作服務層的影響，後者看 identity 基礎層的失效。它也能和 Slack 一起讀，因為兩者都在說明當通訊平台出事時，客戶需要的是清楚的狀態與替代流程，而不是只有技術術語。</p>
<h2 id="代表樣本">代表樣本</h2>
<ul>
<li>Exchange Online 大規模失效代表企業通訊與協作服務的廣域影響。</li>
<li>Teams 全球失效則顯示 IR 通訊本身也會受到通訊工具失效的影響。</li>
<li>service health 與 PIR 的公開格式會影響客戶判讀速度。</li>
<li>tenant-level 與 region-level 失效要分開看。</li>
<li><a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> 讓 Microsoft 能把復原流程標準化。</li>
<li>built-in service resiliency 是企業 SaaS 的預設期待。</li>
<li>shared productivity suite 讓一個服務失效就能放大成企業生產力問題。</li>
<li>customer communication 與技術復原並行，才能避免恢復過程的資訊落差。</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/" data-link-title="Microsoft 365：套件級身分驗證事故" data-link-desc="企業套件在身份依賴失效時，如何同步處理跨產品影響與對外揭露。">M365-1</a></td>
          <td>套件級身份事故</td>
          <td>將跨產品影響分層並同步對外通訊與回復順序</td>
      </tr>
  </tbody>
</table>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-platform-service-description/service-health-and-continuity?country=au&amp;culture=en-au">Service health and continuity</a>：M365 服務健康、PIR 與通訊政策。</li>
<li><a href="https://learn.microsoft.com/microsoft-365/enterprise/view-service-health?view=o365-worldwide">How to check Microsoft 365 service health</a>：Service health 的使用方式。</li>
<li><a href="https://learn.microsoft.com/en-us/services-hub/unified/health/ir-m365">Microsoft 365 incident readiness - Unified</a>：Microsoft 的 incident readiness / PIR 流程。</li>
<li><a href="https://learn.microsoft.com/en-us/compliance/assurance/assurance-m365-service-resiliency?source=recommendations">Built-in service resiliency in Microsoft 365</a>：M365 服務韌性與 downtime 定義。</li>
</ul>
]]></content:encoded></item><item><title>3.C24 SoundCloud：AMQP fan-out 音訊處理 pipeline</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/</guid><description>&lt;p>這個案例的核心責任是說明 fan-out 處理 pipeline 該按處理類型拆隊列、不該共用 queue。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>上傳音訊後用 RabbitMQ 觸發 transcode + 波形圖 + follower 通知。當 Skrillex 等大號上傳時、要避免同步寫 Cassandra 千萬次。每秒 20-30,000 條 persistent message。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不同處理類型分開隊列、各自獨立 scale。揭露 fan-out 不是「broadcast 同一份工作」、而是「同事件觸發多種獨立 pipeline」、每種 pipeline 的 throughput / latency 要求不同。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發 / classic queue vs Streams（log fan-out 場景）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blogs.vmware.com/tanzu/scaling-with-rabbitmq-soundcloud">Scaling with RabbitMQ at SoundCloud (VMware Tanzu)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.infoq.com/presentations/amqp-soundcloud/">AMQP at SoundCloud (InfoQ)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 fan-out 處理 pipeline 該按處理類型拆隊列、不該共用 queue。</p>
<h2 id="觀察">觀察</h2>
<p>上傳音訊後用 RabbitMQ 觸發 transcode + 波形圖 + follower 通知。當 Skrillex 等大號上傳時、要避免同步寫 Cassandra 千萬次。每秒 20-30,000 條 persistent message。</p>
<h2 id="判讀">判讀</h2>
<p>不同處理類型分開隊列、各自獨立 scale。揭露 fan-out 不是「broadcast 同一份工作」、而是「同事件觸發多種獨立 pipeline」、每種 pipeline 的 throughput / latency 要求不同。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發 / classic queue vs Streams（log fan-out 場景）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blogs.vmware.com/tanzu/scaling-with-rabbitmq-soundcloud">Scaling with RabbitMQ at SoundCloud (VMware Tanzu)</a></li>
<li><a href="https://www.infoq.com/presentations/amqp-soundcloud/">AMQP at SoundCloud (InfoQ)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/</guid><description>&lt;p>這個案例的核心責任是說明 B2B SaaS 平台的容量規劃跟 C2C 案例的本質差異。Genesys 服務的是 &lt;em>客戶服務中心&lt;/em> — 客戶停線 = 全終端使用者打不通電話、客戶會失去信任。99.999% 可用性（年停機 5 分鐘）對 B2B 客服 SaaS 是合約義務、不是行銷敘述。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Genesys Cloud 在 DynamoDB 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys DynamoDB Case Study&lt;/a>）：&lt;/p>
&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>8,000+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務國家&lt;/td>
 &lt;td>100+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主 region&lt;/td>
 &lt;td>15 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>衛星 region&lt;/td>
 &lt;td>5 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>99.999%（截至 2024-07-31 的 12 個月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數&lt;/td>
 &lt;td>數百個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料層&lt;/td>
 &lt;td>DynamoDB 為預設、用其他要 justify&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵架構決策（引述 Chief Architect Rob Gevers）：「Amazon DynamoDB is our primary data layer by default, and teams have to justify the use of something else.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Genesys 案例揭露三個 B2B SaaS 平台容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>B2B 可用性目標跟 C2C 不同&lt;/strong>：B2C 大型網站可能接受 99.9%（年停機 8.76 小時）、B2B SaaS 經常合約規定 99.95% 或 99.99%、客服平台類甚至要 99.999%（年停機 5 分鐘）。每多一個 9、容量規劃跟運維成本指數成長。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 SLO 等級設計。&lt;/li>
&lt;li>&lt;strong>「DynamoDB 為預設、用其他要 justify」是規模化平台的工程治理&lt;/strong>：跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix&lt;/a> 整合到 Aurora 是同樣訴求、不同實作 — Genesys 選 DynamoDB 為基準是因為「Multi-region active-active」+「自動 scaling」+「99.999% SLA」的組合最容易達成 5 個 9 目標。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 DB 預設選型。&lt;/li>
&lt;li>&lt;strong>15 主 region + 5 衛星 region = 全球客戶就近接入&lt;/strong>：客戶服務有強烈延遲敏感（agent 操作介面卡 1 秒、客服效率掉一半）、必須在客戶所在地有 region。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster&lt;/a> 的延遲驅動 region 部署同類思維。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的地理分散規劃。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「99.999% over 12 months」是 &lt;em>截至特定時間點的歷史值&lt;/em>、不代表「未來持續達成」。可用性是滾動指標、不是恆久承諾。&lt;/li>
&lt;li>案例 &lt;em>沒有&lt;/em> 提具體 QPS / RPS、訊息量、延遲分布。讀者要對 &lt;em>策略&lt;/em> 學習、具體數字需要自己壓測。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 B2B SaaS 平台的容量規劃跟 C2C 案例的本質差異。Genesys 服務的是 <em>客戶服務中心</em> — 客戶停線 = 全終端使用者打不通電話、客戶會失去信任。99.999% 可用性（年停機 5 分鐘）對 B2B 客服 SaaS 是合約義務、不是行銷敘述。</p>
<h2 id="觀察">觀察</h2>
<p>Genesys Cloud 在 DynamoDB 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys DynamoDB Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶組織</td>
          <td>8,000+ 個</td>
      </tr>
      <tr>
          <td>服務國家</td>
          <td>100+ 個</td>
      </tr>
      <tr>
          <td>主 region</td>
          <td>15 個</td>
      </tr>
      <tr>
          <td>衛星 region</td>
          <td>5 個</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>99.999%（截至 2024-07-31 的 12 個月）</td>
      </tr>
      <tr>
          <td>微服務數</td>
          <td>數百個</td>
      </tr>
      <tr>
          <td>資料層</td>
          <td>DynamoDB 為預設、用其他要 justify</td>
      </tr>
  </tbody>
</table>
<p>關鍵架構決策（引述 Chief Architect Rob Gevers）：「Amazon DynamoDB is our primary data layer by default, and teams have to justify the use of something else.」</p>
<h2 id="判讀">判讀</h2>
<p>Genesys 案例揭露三個 B2B SaaS 平台容量規劃重點。</p>
<ol>
<li><strong>B2B 可用性目標跟 C2C 不同</strong>：B2C 大型網站可能接受 99.9%（年停機 8.76 小時）、B2B SaaS 經常合約規定 99.95% 或 99.99%、客服平台類甚至要 99.999%（年停機 5 分鐘）。每多一個 9、容量規劃跟運維成本指數成長。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 SLO 等級設計。</li>
<li><strong>「DynamoDB 為預設、用其他要 justify」是規模化平台的工程治理</strong>：跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 整合到 Aurora 是同樣訴求、不同實作 — Genesys 選 DynamoDB 為基準是因為「Multi-region active-active」+「自動 scaling」+「99.999% SLA」的組合最容易達成 5 個 9 目標。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 DB 預設選型。</li>
<li><strong>15 主 region + 5 衛星 region = 全球客戶就近接入</strong>：客戶服務有強烈延遲敏感（agent 操作介面卡 1 秒、客服效率掉一半）、必須在客戶所在地有 region。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a> 的延遲驅動 region 部署同類思維。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的地理分散規劃。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「99.999% over 12 months」是 <em>截至特定時間點的歷史值</em>、不代表「未來持續達成」。可用性是滾動指標、不是恆久承諾。</li>
<li>案例 <em>沒有</em> 提具體 QPS / RPS、訊息量、延遲分布。讀者要對 <em>策略</em> 學習、具體數字需要自己壓測。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>B2B SaaS 平台優先選 multi-region active-active 資料層</strong>：DynamoDB Global Tables、Cosmos DB Multi-Region Write、Spanner multi-region 都是候選。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的全球一致性取捨。</li>
<li><strong>「預設 DB」原則簡化 onboarding</strong>：新團隊不用評估十種 DB、預設用 X、特殊需求再 justify。減少團隊認知負擔、加速產品開發。對應 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 的 DB 整合。</li>
<li><strong>99.999% 必須有 redundancy 在每一層</strong>：DNS、load balancer、application、database、storage 都要跨 region active-active。任何一層 single-region 就破壞整體 SLO。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 跟 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06 可靠性驗證模組</a>。</li>
<li><strong>多 region 是成本 vs 可用性的硬取捨</strong>：15 個 region 的成本約是 1 個 region 的 15 倍 — 對 B2B SaaS 是合理投資、對 B2C 通常不划算。</li>
</ol>
<p>跨平台等效：Azure Cosmos DB Multi-Region Write、GCP Spanner multi-region、Cassandra multi-DC 都可實作對等架構。差異是 region 數量、SLA 承諾、跨 region 延遲。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 B2B SaaS 可用性 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget 政策</a></li>
<li>想設計多 region 資料層 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做 DB 統一治理 → <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想規劃跨 region 容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a></li>
<li>想理解 DynamoDB 99.999% 背後的 partition / GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想對應 global tables 多 region 寫衝突 → <a href="/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/" data-link-title="DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例" data-link-desc="Global Tables 不只是 conflict 痛點、也是 cross-device sync / global read / DR failover 的正向工程方案；本文展開 B2B SaaS vs B2C 業務 driver、LWW conflict resolution、reconciliation pipeline，含 Genesys 99.999% 跨 15 region 跟 Disney&#43; 跨裝置同步的對照">DynamoDB global tables 寫衝突</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys Achieves 99.999% Availability Using Amazon DynamoDB</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
</ul>
]]></content:encoded></item><item><title>3.C25 Indeed：Delay queue + DLQ 三層 escalation</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/</guid><description>&lt;p>這個案例的核心責任是說明 retry 策略要跟 queue 拓樸結合設計，分層延遲 + &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ&lt;/a> 的三層 escalation 能避免 head-of-line blocking。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Indeed 是全球最大的求職搜尋引擎之一，每天處理 35M+ 筆職缺資料的索引、更新與推送。職缺資料從雇主端進入系統後，需要經過解析、標準化、索引、推送到搜尋引擎等多個處理步驟，每個步驟由 RabbitMQ 串接的 consumer 處理。&lt;/p>
&lt;p>這個規模下，任何一個處理步驟的暫時失敗（downstream service timeout、資料格式異常、外部 API rate limit）都會產生需要 retry 的訊息。每天有數十萬筆訊息需要至少一次 retry。&lt;/p>
&lt;h2 id="技術挑戰head-of-line-blocking">技術挑戰：Head-of-line blocking&lt;/h2>
&lt;p>Indeed 原本的 retry 策略是 consumer 處理失敗時把訊息 requeue（&lt;code>basic.nack&lt;/code> with &lt;code>requeue=true&lt;/code>）。RabbitMQ 的 requeue 行為是把訊息放回 queue 的 head — 下一次 consumer 拿到的還是這條失敗的訊息。&lt;/p>
&lt;p>當一條訊息因為 downstream timeout 反覆失敗時，它會持續佔住 queue head，阻塞後面所有等待處理的訊息。單一 consumer 的時間被一條失敗訊息反覆消耗，其他正常的訊息延遲累積。在 35M+ 筆/天的吞吐量下，一條 head-of-line blocking 訊息就能讓整個 pipeline 的 processing lag 從秒級升到分鐘級。&lt;/p>
&lt;p>這個問題的根源是 retry 策略跟 queue 拓樸耦合在一起 — requeue 把 retry 決策留在同一個 queue 裡，讓失敗訊息跟正常訊息搶同一條通道。&lt;/p>
&lt;h2 id="解法三層-escalation">解法：三層 escalation&lt;/h2>
&lt;p>Indeed 設計了一個三層 escalation 模型，把失敗訊息依嚴重程度逐層隔離：&lt;/p>
&lt;h3 id="第一層immediate-retry同-queue">第一層：Immediate retry（同 queue）&lt;/h3>
&lt;p>Consumer 處理失敗時，先在 client 端做短暫 backoff（數百毫秒到數秒），然後 ack 原訊息、重新 publish 到同一個 queue 的 tail（而非 requeue 到 head）。&lt;/p>
&lt;p>這層處理的是暫態錯誤 — downstream 偶發的 500、短暫的 network hiccup。多數訊息在第一層就能恢復。重新 publish 到 tail 確保失敗訊息排在正常訊息後面，不阻塞其他訊息。&lt;/p>
&lt;h3 id="第二層delay-queue">第二層：Delay queue&lt;/h3>
&lt;p>第一層 retry N 次仍然失敗的訊息，透過 RabbitMQ 的 Dead Letter Exchange（DLX）路由到 delay queue。Delay queue 用 &lt;code>x-message-ttl&lt;/code> 設定延遲時間（例如 30 秒、1 分鐘、5 分鐘），TTL 到期後訊息透過另一個 DLX 路由回原始 queue 的 tail。&lt;/p>
&lt;p>Indeed 用多個不同 TTL 的 delay queue 實作 exponential backoff — 第一次進 delay 等 30 秒、第二次等 1 分鐘、第三次等 5 分鐘。這個做法利用 RabbitMQ 原生的 DLX + TTL 機制，不需要額外的 scheduler 或 cron job。&lt;/p>
&lt;p>這層處理的是持續性錯誤 — downstream 在做 deployment、外部 API 在做 maintenance。延遲重試讓 downstream 有時間恢復，同時失敗訊息完全離開主 queue、不影響正常處理。&lt;/p>
&lt;h3 id="第三層dead-letter-queue">第三層：Dead Letter Queue&lt;/h3>
&lt;p>Delay queue retry M 次後仍然失敗的訊息進入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ&lt;/a>。DLQ 中的訊息不再自動重試，需要人工審視或批次 replay。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 retry 策略要跟 queue 拓樸結合設計，分層延遲 + <a href="/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ</a> 的三層 escalation 能避免 head-of-line blocking。</p>
<h2 id="業務背景">業務背景</h2>
<p>Indeed 是全球最大的求職搜尋引擎之一，每天處理 35M+ 筆職缺資料的索引、更新與推送。職缺資料從雇主端進入系統後，需要經過解析、標準化、索引、推送到搜尋引擎等多個處理步驟，每個步驟由 RabbitMQ 串接的 consumer 處理。</p>
<p>這個規模下，任何一個處理步驟的暫時失敗（downstream service timeout、資料格式異常、外部 API rate limit）都會產生需要 retry 的訊息。每天有數十萬筆訊息需要至少一次 retry。</p>
<h2 id="技術挑戰head-of-line-blocking">技術挑戰：Head-of-line blocking</h2>
<p>Indeed 原本的 retry 策略是 consumer 處理失敗時把訊息 requeue（<code>basic.nack</code> with <code>requeue=true</code>）。RabbitMQ 的 requeue 行為是把訊息放回 queue 的 head — 下一次 consumer 拿到的還是這條失敗的訊息。</p>
<p>當一條訊息因為 downstream timeout 反覆失敗時，它會持續佔住 queue head，阻塞後面所有等待處理的訊息。單一 consumer 的時間被一條失敗訊息反覆消耗，其他正常的訊息延遲累積。在 35M+ 筆/天的吞吐量下，一條 head-of-line blocking 訊息就能讓整個 pipeline 的 processing lag 從秒級升到分鐘級。</p>
<p>這個問題的根源是 retry 策略跟 queue 拓樸耦合在一起 — requeue 把 retry 決策留在同一個 queue 裡，讓失敗訊息跟正常訊息搶同一條通道。</p>
<h2 id="解法三層-escalation">解法：三層 escalation</h2>
<p>Indeed 設計了一個三層 escalation 模型，把失敗訊息依嚴重程度逐層隔離：</p>
<h3 id="第一層immediate-retry同-queue">第一層：Immediate retry（同 queue）</h3>
<p>Consumer 處理失敗時，先在 client 端做短暫 backoff（數百毫秒到數秒），然後 ack 原訊息、重新 publish 到同一個 queue 的 tail（而非 requeue 到 head）。</p>
<p>這層處理的是暫態錯誤 — downstream 偶發的 500、短暫的 network hiccup。多數訊息在第一層就能恢復。重新 publish 到 tail 確保失敗訊息排在正常訊息後面，不阻塞其他訊息。</p>
<h3 id="第二層delay-queue">第二層：Delay queue</h3>
<p>第一層 retry N 次仍然失敗的訊息，透過 RabbitMQ 的 Dead Letter Exchange（DLX）路由到 delay queue。Delay queue 用 <code>x-message-ttl</code> 設定延遲時間（例如 30 秒、1 分鐘、5 分鐘），TTL 到期後訊息透過另一個 DLX 路由回原始 queue 的 tail。</p>
<p>Indeed 用多個不同 TTL 的 delay queue 實作 exponential backoff — 第一次進 delay 等 30 秒、第二次等 1 分鐘、第三次等 5 分鐘。這個做法利用 RabbitMQ 原生的 DLX + TTL 機制，不需要額外的 scheduler 或 cron job。</p>
<p>這層處理的是持續性錯誤 — downstream 在做 deployment、外部 API 在做 maintenance。延遲重試讓 downstream 有時間恢復，同時失敗訊息完全離開主 queue、不影響正常處理。</p>
<h3 id="第三層dead-letter-queue">第三層：Dead Letter Queue</h3>
<p>Delay queue retry M 次後仍然失敗的訊息進入 <a href="/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ</a>。DLQ 中的訊息不再自動重試，需要人工審視或批次 replay。</p>
<p>DLQ 的價值是把「目前無法處理」的訊息安全保存，不讓它們無限消耗 retry 資源。Indeed 的維運團隊定期檢查 DLQ 中的訊息 — 按 error type 分群、判斷是 bug（需要修 code 再 replay）還是資料問題（需要修正資料再 replay）。</p>
<h2 id="取捨">取捨</h2>
<p><strong>犧牲的是 delivery order</strong>。訊息從 delay queue 回到主 queue tail 時，已經不在原始的位置。對 Indeed 的職缺處理來說，order 不影響正確性 — 職缺更新是 idempotent 的，最終狀態正確即可。對 order-sensitive 的場景，這個模型需要額外的 ordering 機制。</p>
<p><strong>增加的是拓樸複雜度</strong>。三層 escalation 涉及主 queue + 多個 delay queue + DLQ + 多個 DLX 的 binding。RabbitMQ 的 exchange / queue / binding 組合需要明確規劃跟文件化，否則維運時搞不清楚訊息的路由路徑。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：DLX + TTL 是 RabbitMQ 原生的 durable 機制</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：retry 策略跟 consumer 的 ack/nack 行為</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/dlq-retry-escalation/" data-link-title="RabbitMQ DLQ 與分層 retry：別把失敗訊息 requeue 回隊首" data-link-desc="RabbitMQ 處理失敗訊息最常見的錯是直接 requeue 回原隊列——它回到隊首、反覆失敗、把後面的訊息全卡住（head-of-line blocking）。正解是用 dead-letter exchange &#43; TTL 組出 work → delay → DLQ 的分層 escalation。本文展開 DLX 求值模型、實機驗證的三層拓樸、5 個把 retry 寫成無限迴圈與隊列阻塞的 production 踩坑，以及 retry 拓樸的容量邊界">RabbitMQ DLQ retry escalation</a>：DLX 配置的實作細節</li>
<li><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6 Uber Kafka 平台</a>：Kafka 生態的 retry topic 跟 DLQ 設計比較</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>Consumer 的 processing lag 在特定時段突然升高、但訊息產生速率沒變</li>
<li>同一條訊息的 retry 佔據 consumer 的大部分處理時間</li>
<li>Requeue 後訊息立刻又被同一個 consumer 取到、進入 retry 迴圈</li>
<li>DLQ 中的訊息堆積、沒有定期審視跟 replay 的機制</li>
<li>Retry 策略只有 client 端 backoff、沒有 queue 拓樸層面的隔離</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.indeedblog.com/blog/2017/06/delaying-messages/">Delaying Messages with RabbitMQ at Indeed</a></li>
<li><a href="https://engineering.indeedblog.com/talks/get-job-35-million-times-day-using-rabbitmq/">Get a Job 35 Million Times a Day Using RabbitMQ (talk)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/</guid><description>&lt;p>這個案例的核心責任是說明「ML feature store 的延遲敏感層」工程選型。即時推薦（首頁 carousel、播放後下一支）需要在 100ms 內生成、ML inference 之前的 feature lookup 通常吃 30-50ms — 把 lookup 壓到 10ms 以下、整個推薦延遲才有預算空間。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Tubi 在 ElastiCache 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/elasticache/customers/">ElastiCache Customers&lt;/a>）：&lt;/p>
&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>ML inference feature store&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>p99 延遲&lt;/td>
 &lt;td>&amp;lt; 10 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移路徑&lt;/td>
 &lt;td>ScyllaDB → ElastiCache for Redis&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務場景&lt;/td>
 &lt;td>串流推薦（free streaming service）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Tubi 案例揭露三個 ML feature store 容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>feature store 是 ML inference 的 critical path&lt;/strong>：每個推薦請求都要查 N 個 feature（user_profile、item_metadata、recent_interactions、similar_users 等）、每個 feature 查詢都吃 latency budget。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的多 stage budget 分解。&lt;/li>
&lt;li>&lt;strong>ScyllaDB → ElastiCache 是「持久 KV → 純 cache」的權衡&lt;/strong>：ScyllaDB 是 Cassandra-compatible 高吞吐 KV、提供 durability；ElastiCache 是 in-memory cache、可以 cache miss。Tubi 選 cache 是判斷「feature 可以重新計算」、durability 不必、純 in-memory 更快。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache vs durable store 選型。&lt;/li>
&lt;li>&lt;strong>p99 才是 ML 系統的容量門檻&lt;/strong>：ML 系統的 user-perceived latency 是 &lt;em>最後完成的 inference&lt;/em>、不是平均。p50 快沒用、p99 慢用戶就看到 loading spinner。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 latency percentile 分析、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 的長尾延遲議題同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「sub-10ms p99」沒指明 &lt;em>p999 / p9999&lt;/em>。p9999 通常比 p99 高一個量級、會出現在實際 user-perceived 體驗。&lt;/li>
&lt;li>ElastiCache 的 sub-10ms 是 &lt;em>cache hit 路徑&lt;/em> — cache miss 路徑會回到 ScyllaDB 或重新計算、延遲可能 100ms+。容量規劃要考慮 cache hit rate 跟 miss recovery 兩條路徑。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>ML feature store 用「兩層 cache」設計&lt;/strong>：L1 是 in-process cache（最熱的 features）、L2 是 ElastiCache / Memcached（次熱）、L3 才是持久 store（ScyllaDB / DynamoDB / S3 + Parquet）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache hierarchy。&lt;/li>
&lt;li>&lt;strong>feature 可重算 → 用 cache、feature 必須持久 → 用 store&lt;/strong>：判斷依據是「重算成本」跟「資料一致性需求」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>p99 / p999 反推單個 stage latency 上限&lt;/strong>：每個 stage（network、cache lookup、feature aggregation、model inference、response serialization）給一個 latency budget、總和等於整體 SLO。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 同樣的反推思維。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS ElastiCache for Redis / Valkey / MemoryDB、GCP Memorystore for Redis、Azure Cache for Redis 都可實作對等架構。專為 ML feature store 設計的還有 Feast / Tecton / Hopsworks 等開源 + 商業方案、底層常用 Redis-compatible store。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「ML feature store 的延遲敏感層」工程選型。即時推薦（首頁 carousel、播放後下一支）需要在 100ms 內生成、ML inference 之前的 feature lookup 通常吃 30-50ms — 把 lookup 壓到 10ms 以下、整個推薦延遲才有預算空間。</p>
<h2 id="觀察">觀察</h2>
<p>Tubi 在 ElastiCache 的關鍵敘述（引自 <a href="https://aws.amazon.com/elasticache/customers/">ElastiCache Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>工作負載</td>
          <td>ML inference feature store</td>
      </tr>
      <tr>
          <td>p99 延遲</td>
          <td>&lt; 10 ms</td>
      </tr>
      <tr>
          <td>遷移路徑</td>
          <td>ScyllaDB → ElastiCache for Redis</td>
      </tr>
      <tr>
          <td>業務場景</td>
          <td>串流推薦（free streaming service）</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<p>Tubi 案例揭露三個 ML feature store 容量設計重點。</p>
<ol>
<li><strong>feature store 是 ML inference 的 critical path</strong>：每個推薦請求都要查 N 個 feature（user_profile、item_metadata、recent_interactions、similar_users 等）、每個 feature 查詢都吃 latency budget。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的多 stage budget 分解。</li>
<li><strong>ScyllaDB → ElastiCache 是「持久 KV → 純 cache」的權衡</strong>：ScyllaDB 是 Cassandra-compatible 高吞吐 KV、提供 durability；ElastiCache 是 in-memory cache、可以 cache miss。Tubi 選 cache 是判斷「feature 可以重新計算」、durability 不必、純 in-memory 更快。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache vs durable store 選型。</li>
<li><strong>p99 才是 ML 系統的容量門檻</strong>：ML 系統的 user-perceived latency 是 <em>最後完成的 inference</em>、不是平均。p50 快沒用、p99 慢用戶就看到 loading spinner。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 latency percentile 分析、跟 <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> 的長尾延遲議題同類。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「sub-10ms p99」沒指明 <em>p999 / p9999</em>。p9999 通常比 p99 高一個量級、會出現在實際 user-perceived 體驗。</li>
<li>ElastiCache 的 sub-10ms 是 <em>cache hit 路徑</em> — cache miss 路徑會回到 ScyllaDB 或重新計算、延遲可能 100ms+。容量規劃要考慮 cache hit rate 跟 miss recovery 兩條路徑。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>ML feature store 用「兩層 cache」設計</strong>：L1 是 in-process cache（最熱的 features）、L2 是 ElastiCache / Memcached（次熱）、L3 才是持久 store（ScyllaDB / DynamoDB / S3 + Parquet）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache hierarchy。</li>
<li><strong>feature 可重算 → 用 cache、feature 必須持久 → 用 store</strong>：判斷依據是「重算成本」跟「資料一致性需求」。對應 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a>。</li>
<li><strong>p99 / p999 反推單個 stage latency 上限</strong>：每個 stage（network、cache lookup、feature aggregation、model inference、response serialization）給一個 latency budget、總和等於整體 SLO。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>、跟 <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> 同樣的反推思維。</li>
</ol>
<p>跨平台等效：AWS ElastiCache for Redis / Valkey / MemoryDB、GCP Memorystore for Redis、Azure Cache for Redis 都可實作對等架構。專為 ML feature store 設計的還有 Feast / Tecton / Hopsworks 等開源 + 商業方案、底層常用 Redis-compatible store。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 ML feature store → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想做 p99 / p999 反推 → <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>對照其他 cache 案例 → <a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder ElastiCache</a>（配對引擎）</li>
<li>想理解 cache hierarchy → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/elasticache/customers/">Amazon ElastiCache Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/build-an-ultra-low-latency-online-feature-store-for-real-time-inferencing-using-amazon-elasticache-for-redis/">Build an ultra-low latency online feature store for real-time inferencing using Amazon ElastiCache for Redis</a></li>
</ul>
]]></content:encoded></item><item><title>3.C26 GoCardless：Hutch + 單一 topic exchange service mesh</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/</guid><description>&lt;p>這個案例的核心責任是說明小規模時單 vhost + 統一 routing key 規範可作為 service mesh 基礎。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>單一 RabbitMQ cluster 作為所有服務之間的通訊中樞、自家 Hutch（Ruby lib）2013 從 production 抽出開源。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>routing key 格式 &lt;code>service.subject.action&lt;/code>（如 &lt;code>paysvc.payment.chargedback&lt;/code>）、單一 topic exchange、JSON 序列化（多語言可讀）。揭露小規模單 cluster 可以用「routing key 命名規範」取代複雜 exchange 拓樸。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Exchange types 與 routing 設計 / 多 vhost（單 vhost 服務 mesh 的反向案例）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &amp;#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg&lt;/a>（規模化後的對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://gocardless.com/blog/hutch-inter-service-communication-with-rabbitmq/">Hutch: Inter-Service Communication with RabbitMQ&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明小規模時單 vhost + 統一 routing key 規範可作為 service mesh 基礎。</p>
<h2 id="觀察">觀察</h2>
<p>單一 RabbitMQ cluster 作為所有服務之間的通訊中樞、自家 Hutch（Ruby lib）2013 從 production 抽出開源。</p>
<h2 id="判讀">判讀</h2>
<p>routing key 格式 <code>service.subject.action</code>（如 <code>paysvc.payment.chargedback</code>）、單一 topic exchange、JSON 序列化（多語言可讀）。揭露小規模單 cluster 可以用「routing key 命名規範」取代複雜 exchange 拓樸。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Exchange types 與 routing 設計 / 多 vhost（單 vhost 服務 mesh 的反向案例）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg</a>（規模化後的對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://gocardless.com/blog/hutch-inter-service-communication-with-rabbitmq/">Hutch: Inter-Service Communication with RabbitMQ</a></li>
</ul>
]]></content:encoded></item><item><title>9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/</guid><description>&lt;p>這個案例的核心責任是說明「行動支付類 SaaS」的訊息工作負載特性。PayPay 是日本最大行動支付（pre-IPO 估值 70 億美金級）、訊息功能需要在每筆交易後即時通知（付款成功、收款、優惠券）、單一用戶每天可能收到數十條訊息、加總到平台級別就是每日上億訊息。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PayPay 在 DynamoDB 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&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>3 億訊息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要工作負載&lt;/td>
 &lt;td>行動支付通知 + 訊息功能&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可靠性敘述&lt;/td>
 &lt;td>「Super reliable and performed consistently」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>日本&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>PayPay 案例揭露三個行動支付訊息系統的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>支付通知是「不可丟失 + 不可延遲」雙重需求&lt;/strong>：用戶付完款 30 秒沒收到通知會懷疑系統壞了、會打客服 / 重複扣款。這層需求比 OTA 推播嚴格、必須有 durable queue + retry + 重複偵測。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 idempotency 設計。&lt;/li>
&lt;li>&lt;strong>DynamoDB 在「訊息事件」這類負載特別適合&lt;/strong>：每則訊息有獨立 message_id（partition key 天然均勻）、TTL 機制可以自動清理過期訊息（避免 storage 爆炸）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 partition 均勻優勢、跟 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a> 的 TTL 議題。&lt;/li>
&lt;li>&lt;strong>3 億 / 天 ≈ 3,500 訊息 / 秒平均&lt;/strong>：聽起來不大、但這是 &lt;em>平均&lt;/em>。月底、雙 11 類大促、新年紅包等場景、單秒峰值可能達 10x-50x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的峰均比評估。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「super reliable」是行銷語言、不是工程承諾。讀此類短篇案例要把行銷敘述折扣、重點看 &lt;em>服務組合&lt;/em> 與 &lt;em>規模量級&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>訊息系統設計區分「通知」跟「訊息」&lt;/strong>：通知（payment received）是 transactional、不可丟失；訊息（marketing）可以丟失部分、重點是 throughput。兩者用不同 SLO、不同 storage。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的訊息分類。&lt;/li>
&lt;li>&lt;strong>TTL 自動清理避免 storage 成本爆炸&lt;/strong>：3 億 / 天 × 30 天 = 90 億筆記錄、不清理會撐死 storage 預算。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 TTL 設計。&lt;/li>
&lt;li>&lt;strong>訊息推送的下游（APNs、FCM、SMS gateway）是隱性瓶頸&lt;/strong>：DynamoDB 寫入可以撐 3K msg/sec、但 APNs 一天的 quota 是有限的。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的依賴鏈分析。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Firestore + Cloud Messaging、Azure Cosmos DB + Notification Hubs 都是對等架構。差異是 vendor 整合度跟全球分發能力。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計行動支付訊息 → &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>&lt;/li>
&lt;li>對照其他 KV 高吞吐 → &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom&lt;/a>&lt;/li>
&lt;li>想做訊息系統容量規劃 → &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a>&lt;/li>
&lt;li>想避免訊息熱點打爆單一 partition → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &amp;#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式&lt;/a>&lt;/li>
&lt;li>想評估訊息系統的 capacity mode → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/paypay/">PayPay on AWS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「行動支付類 SaaS」的訊息工作負載特性。PayPay 是日本最大行動支付（pre-IPO 估值 70 億美金級）、訊息功能需要在每筆交易後即時通知（付款成功、收款、優惠券）、單一用戶每天可能收到數十條訊息、加總到平台級別就是每日上億訊息。</p>
<h2 id="觀察">觀察</h2>
<p>PayPay 在 DynamoDB 的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每日訊息量</td>
          <td>3 億訊息</td>
      </tr>
      <tr>
          <td>主要工作負載</td>
          <td>行動支付通知 + 訊息功能</td>
      </tr>
      <tr>
          <td>可靠性敘述</td>
          <td>「Super reliable and performed consistently」</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>日本</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<p>PayPay 案例揭露三個行動支付訊息系統的工程重點。</p>
<ol>
<li><strong>支付通知是「不可丟失 + 不可延遲」雙重需求</strong>：用戶付完款 30 秒沒收到通知會懷疑系統壞了、會打客服 / 重複扣款。這層需求比 OTA 推播嚴格、必須有 durable queue + retry + 重複偵測。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 idempotency 設計。</li>
<li><strong>DynamoDB 在「訊息事件」這類負載特別適合</strong>：每則訊息有獨立 message_id（partition key 天然均勻）、TTL 機制可以自動清理過期訊息（避免 storage 爆炸）。對應 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 的 partition 均勻優勢、跟 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a> 的 TTL 議題。</li>
<li><strong>3 億 / 天 ≈ 3,500 訊息 / 秒平均</strong>：聽起來不大、但這是 <em>平均</em>。月底、雙 11 類大促、新年紅包等場景、單秒峰值可能達 10x-50x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的峰均比評估。</li>
</ol>
<p>需要警惕：「super reliable」是行銷語言、不是工程承諾。讀此類短篇案例要把行銷敘述折扣、重點看 <em>服務組合</em> 與 <em>規模量級</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>訊息系統設計區分「通知」跟「訊息」</strong>：通知（payment received）是 transactional、不可丟失；訊息（marketing）可以丟失部分、重點是 throughput。兩者用不同 SLO、不同 storage。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的訊息分類。</li>
<li><strong>TTL 自動清理避免 storage 成本爆炸</strong>：3 億 / 天 × 30 天 = 90 億筆記錄、不清理會撐死 storage 預算。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 TTL 設計。</li>
<li><strong>訊息推送的下游（APNs、FCM、SMS gateway）是隱性瓶頸</strong>：DynamoDB 寫入可以撐 3K msg/sec、但 APNs 一天的 quota 是有限的。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的依賴鏈分析。</li>
</ol>
<p>跨平台等效：GCP Firestore + Cloud Messaging、Azure Cosmos DB + Notification Hubs 都是對等架構。差異是 vendor 整合度跟全球分發能力。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計行動支付訊息 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>對照其他 KV 高吞吐 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> / <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a></li>
<li>想做訊息系統容量規劃 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a></li>
<li>想避免訊息熱點打爆單一 partition → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估訊息系統的 capacity mode → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/paypay/">PayPay on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C27 Zalando：RabbitMQ on AWS 自動化 master selection</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/</guid><description>&lt;p>這個案例的核心責任是說明雲端 cluster 治理在 K8s 之前的工程模式。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Communication platform 用 RabbitMQ cluster、跑在 EC2 / Docker container 上、用 supervisord 並行 sidekick + RabbitMQ。AWS 帳號限制每 region 5 個 Elastic IP。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、master 死後晉升下一個最老 node。跨版本升級用 federation 上游接到新 cluster 過渡。揭露「cluster master selection」跟「IP 限制」是雲端部署的早期關鍵限制。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Erlang clustering + network partition / Federation + Shovel / RabbitMQ Cluster Operator（K8s 之前的雲端 cluster 治理）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.zalando.com/posts/2018/02/rabbit-in-the-cloud.html">Rabbit in the Cloud (Zalando Engineering)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明雲端 cluster 治理在 K8s 之前的工程模式。</p>
<h2 id="觀察">觀察</h2>
<p>Communication platform 用 RabbitMQ cluster、跑在 EC2 / Docker container 上、用 supervisord 並行 sidekick + RabbitMQ。AWS 帳號限制每 region 5 個 Elastic IP。</p>
<h2 id="判讀">判讀</h2>
<p>自建 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、master 死後晉升下一個最老 node。跨版本升級用 federation 上游接到新 cluster 過渡。揭露「cluster master selection」跟「IP 限制」是雲端部署的早期關鍵限制。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Erlang clustering + network partition / Federation + Shovel / RabbitMQ Cluster Operator（K8s 之前的雲端 cluster 治理）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.zalando.com/posts/2018/02/rabbit-in-the-cloud.html">Rabbit in the Cloud (Zalando Engineering)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C27 Disney+：DynamoDB 撐每日數十億動作的觀看歷史</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/</guid><description>&lt;p>這個案例的核心責任是說明「串流平台 metadata 層」的工作負載 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的「live streaming 直播容量」是同產業不同議題。Disney+ 的 metadata 層處理「播了什麼、看到哪、下次推薦什麼」、是串流平台的「control plane」、不是「data plane」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Disney+ 在 DynamoDB 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&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>billions of actions daily&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要工作負載&lt;/td>
 &lt;td>content metadata + watch list management&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>global&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個用戶動作（播放、暫停、跳過、加入 watchlist、評分）都是一次 DynamoDB 寫入。每次打開 app 又是多次讀（自己的 watchlist、最近播放、繼續觀看）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Disney+ 案例揭露三個串流平台 metadata 層的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「每日數十億動作」= read + write 都要撐&lt;/strong>：跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 18:1 讀寫比不同、串流 metadata 通常接近 5:1 read-heavy（每動作 1 寫、每 session 5 讀）。partition key 設計通常用 user_id、天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema design。&lt;/li>
&lt;li>&lt;strong>新片發布是 predictable-peak&lt;/strong>：Marvel / Star Wars / Disney 動畫 新片上線首日、metadata 流量可衝 3-5 倍 — 因為「全平台用戶同時打開該片頁面」。這比一般 Black Friday 集中、像 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的集中型流量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 的內容發布事件容量規劃。&lt;/li>
&lt;li>&lt;strong>watchlist + 播放進度需要跨裝置即時同步&lt;/strong>：用戶在手機看到一半、晚上回家用電視繼續、進度必須跨裝置同步。這層需求對 DynamoDB Global Tables（multi-region active-active）特別適合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的最終一致性可接受場景。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「billions of actions daily」沒指明具體數字（10 億、100 億 還是 數十億？）。讀此類短篇案例只能取「量級對標」、不能套用具體數字。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>串流平台分「metadata 層」「content delivery 層」&lt;/strong>：metadata（watchlist、播放進度、推薦）用 DynamoDB / Cosmos DB；content（video file）用 CDN + S3 / object storage。兩者完全分開、互不影響。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 control plane vs data plane、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom&lt;/a> 的同類思維。&lt;/li>
&lt;li>&lt;strong>新片發布像 mini Black Friday、要 pre-scaling&lt;/strong>：發布時間已知、流量倍數可預估（根據前幾部）、可以提前 1-2 天 pre-scale DynamoDB capacity。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a>。&lt;/li>
&lt;li>&lt;strong>DynamoDB Global Tables 是跨裝置同步的有效方案&lt;/strong>：用戶在不同 region 登入同帳號、寫入會自動同步到其他 region。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys&lt;/a> 的 multi-region active-active。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Netflix 同類 metadata 用 Cassandra + EVCache（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix&lt;/a> 提及）、HBO Max 用 Aurora、Apple TV+ 用 FoundationDB + Cassandra — 各家串流的 metadata 技術棧不同、但「分層解耦」的工程哲學一致。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「串流平台 metadata 層」的工作負載 — 跟 <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a> 的「live streaming 直播容量」是同產業不同議題。Disney+ 的 metadata 層處理「播了什麼、看到哪、下次推薦什麼」、是串流平台的「control plane」、不是「data plane」。</p>
<h2 id="觀察">觀察</h2>
<p>Disney+ 在 DynamoDB 的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每日動作量</td>
          <td>billions of actions daily</td>
      </tr>
      <tr>
          <td>主要工作負載</td>
          <td>content metadata + watch list management</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>global</td>
      </tr>
  </tbody>
</table>
<p>每個用戶動作（播放、暫停、跳過、加入 watchlist、評分）都是一次 DynamoDB 寫入。每次打開 app 又是多次讀（自己的 watchlist、最近播放、繼續觀看）。</p>
<h2 id="判讀">判讀</h2>
<p>Disney+ 案例揭露三個串流平台 metadata 層的工程重點。</p>
<ol>
<li><strong>「每日數十億動作」= read + write 都要撐</strong>：跟 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> 的 18:1 讀寫比不同、串流 metadata 通常接近 5:1 read-heavy（每動作 1 寫、每 session 5 讀）。partition key 設計通常用 user_id、天然均勻、不會 hot partition。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design。</li>
<li><strong>新片發布是 predictable-peak</strong>：Marvel / Star Wars / Disney 動畫 新片上線首日、metadata 流量可衝 3-5 倍 — 因為「全平台用戶同時打開該片頁面」。這比一般 Black Friday 集中、像 <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a> 的集中型流量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的內容發布事件容量規劃。</li>
<li><strong>watchlist + 播放進度需要跨裝置即時同步</strong>：用戶在手機看到一半、晚上回家用電視繼續、進度必須跨裝置同步。這層需求對 DynamoDB Global Tables（multi-region active-active）特別適合。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的最終一致性可接受場景。</li>
</ol>
<p>需要警惕：「billions of actions daily」沒指明具體數字（10 億、100 億 還是 數十億？）。讀此類短篇案例只能取「量級對標」、不能套用具體數字。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>串流平台分「metadata 層」「content delivery 層」</strong>：metadata（watchlist、播放進度、推薦）用 DynamoDB / Cosmos DB；content（video file）用 CDN + S3 / object storage。兩者完全分開、互不影響。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 control plane vs data plane、跟 <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a> 的同類思維。</li>
<li><strong>新片發布像 mini Black Friday、要 pre-scaling</strong>：發布時間已知、流量倍數可預估（根據前幾部）、可以提前 1-2 天 pre-scale DynamoDB capacity。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
<li><strong>DynamoDB Global Tables 是跨裝置同步的有效方案</strong>：用戶在不同 region 登入同帳號、寫入會自動同步到其他 region。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> 的 multi-region active-active。</li>
</ol>
<p>跨平台等效：Netflix 同類 metadata 用 Cassandra + EVCache（<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 提及）、HBO Max 用 Aurora、Apple TV+ 用 FoundationDB + Cassandra — 各家串流的 metadata 技術棧不同、但「分層解耦」的工程哲學一致。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他串流案例 → <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a>（live）/ <a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">9.C29 NTT DOCOMO Lemino</a></li>
<li>想理解 metadata 層 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想做內容發布 pre-scaling → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 Prime Day</a></li>
<li>想做跨裝置同步設計 → <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys multi-region</a></li>
<li>想拆 metadata 的 single-table 與 GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想做跨 region metadata 一致性 → <a href="/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/" data-link-title="DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例" data-link-desc="Global Tables 不只是 conflict 痛點、也是 cross-device sync / global read / DR failover 的正向工程方案；本文展開 B2B SaaS vs B2C 業務 driver、LWW conflict resolution、reconciliation pipeline，含 Genesys 99.999% 跨 15 region 跟 Disney&#43; 跨裝置同步的對照">DynamoDB global tables 寫衝突</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-use-cases-for-media-and-entertainment-customers/">Amazon DynamoDB use cases for media and entertainment customers</a></li>
</ul>
]]></content:encoded></item><item><title>3.C28 WeWork：Consistent hash exchange 保證帳戶順序</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/</guid><description>&lt;p>這個案例的核心責任是說明 RabbitMQ 也能做「per-key ordering」、用 consistent hash exchange 模擬 partition。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>訊息順序對某些業務流程關鍵、但全局排序代價高。WeWork 採固定數量 queue + 用 account ID hash 路由到特定 queue。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>每個 queue 一個 SideKiq worker + exclusive consumer 保證單帳戶順序。文後發現 RabbitMQ Consistent Hashing plugin 已內建類似機制（類似 Kafka 分區）。揭露 partition-level ordering 不是 Kafka 專屬、在 broker model 可用 hash exchange 達成。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Exchange types / Prefetch + consumer 併發（partition-level ordering 模式）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（partition + key 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.cloudamqp.com/blog/weworks-good-enough-order%20guarantee.html">WeWork&amp;rsquo;s &amp;ldquo;Good Enough&amp;rdquo; Order Guarantee&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 RabbitMQ 也能做「per-key ordering」、用 consistent hash exchange 模擬 partition。</p>
<h2 id="觀察">觀察</h2>
<p>訊息順序對某些業務流程關鍵、但全局排序代價高。WeWork 採固定數量 queue + 用 account ID hash 路由到特定 queue。</p>
<h2 id="判讀">判讀</h2>
<p>每個 queue 一個 SideKiq worker + exclusive consumer 保證單帳戶順序。文後發現 RabbitMQ Consistent Hashing plugin 已內建類似機制（類似 Kafka 分區）。揭露 partition-level ordering 不是 Kafka 專屬、在 broker model 可用 hash exchange 達成。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Exchange types / Prefetch + consumer 併發（partition-level ordering 模式）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（partition + key 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cloudamqp.com/blog/weworks-good-enough-order%20guarantee.html">WeWork&rsquo;s &ldquo;Good Enough&rdquo; Order Guarantee</a></li>
</ul>
]]></content:encoded></item><item><title>9.C28 FanDuel：體育直播 + 投注的雙重峰值</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/</guid><description>&lt;p>這個案例的核心責任是說明「雙重峰值對齊」的工程取捨。FanDuel 同時運營體育直播（live streaming）跟體育投注（betting）、兩個工作負載在 &lt;em>同一場 NFL Super Bowl&lt;/em> 同時達到峰值、但 SLO 完全不同 — 直播容忍 30 秒延遲、投注必須毫秒內成交。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FanDuel 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study&lt;/a>）：&lt;/p>
&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>3.5 M+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>美國 20+ 州 + 加拿大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值擴容倍數&lt;/td>
 &lt;td>5-10x（NFL Super Bowl 等大型賽事）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AWS Local Zones + Wavelength + Outposts&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值類型&lt;/td>
 &lt;td>直播 + 投注雙峰&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「seamlessly scale capacity 5–10 times as required for large sporting events, such as the NFL Super Bowl」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>FanDuel 案例揭露三個雙重峰值對齊的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>直播跟投注是兩種完全不同 SLO&lt;/strong>：直播容忍秒級延遲（用 CDN + ABR 串流）、投注必須毫秒級成交（Super Bowl 進球瞬間、賠率變動、用戶投注必須在賠率變化前完成）。兩個服務必須各自獨立擴容、各自獨立 SLO。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的多 SLO 對齊。&lt;/li>
&lt;li>&lt;strong>AWS Local Zones / Wavelength / Outposts 是地理 + 監管雙重需求&lt;/strong>：美國博彩受各州監管、資料必須留在州內 → 用 Local Zones 在每個州就近部署；4G/5G 用戶投注延遲敏感 → 用 Wavelength 在電信商機房內運算；on-prem 需求 → 用 Outposts。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered&lt;/a> 的受監管雙重需求、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games&lt;/a> 的延遲反推 region。&lt;/li>
&lt;li>&lt;strong>5-10x 是「同類事件中的最高倍率」&lt;/strong>：Super Bowl 是 NFL 賽季最大事件、不是常態。平日 baseline → 季後賽 2-3x → 季冠軍賽 4-5x → Super Bowl 5-10x。容量規劃要按事件級別分段、不是一律 10x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的事件型容量分級。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>AWS 案例 &lt;em>沒有&lt;/em> 提具體 betting transaction TPS、concurrent streams、延遲分布。讀者要對 &lt;em>策略&lt;/em> 學習、不要套用具體數字。&lt;/li>
&lt;li>「5-10x」是 &lt;em>峰值倍數&lt;/em>、不是 &lt;em>peak 持續時間&lt;/em>。Super Bowl 的關鍵 30 分鐘可能 8-10x、其他 3 小時可能 3-5x。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「雙重峰值對齊」的工程取捨。FanDuel 同時運營體育直播（live streaming）跟體育投注（betting）、兩個工作負載在 <em>同一場 NFL Super Bowl</em> 同時達到峰值、但 SLO 完全不同 — 直播容忍 30 秒延遲、投注必須毫秒內成交。</p>
<h2 id="觀察">觀察</h2>
<p>FanDuel 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活客戶</td>
          <td>3.5 M+</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>美國 20+ 州 + 加拿大</td>
      </tr>
      <tr>
          <td>峰值擴容倍數</td>
          <td>5-10x（NFL Super Bowl 等大型賽事）</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AWS Local Zones + Wavelength + Outposts</td>
      </tr>
      <tr>
          <td>峰值類型</td>
          <td>直播 + 投注雙峰</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「seamlessly scale capacity 5–10 times as required for large sporting events, such as the NFL Super Bowl」。</p>
<h2 id="判讀">判讀</h2>
<p>FanDuel 案例揭露三個雙重峰值對齊的工程重點。</p>
<ol>
<li><strong>直播跟投注是兩種完全不同 SLO</strong>：直播容忍秒級延遲（用 CDN + ABR 串流）、投注必須毫秒級成交（Super Bowl 進球瞬間、賠率變動、用戶投注必須在賠率變化前完成）。兩個服務必須各自獨立擴容、各自獨立 SLO。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的多 SLO 對齊。</li>
<li><strong>AWS Local Zones / Wavelength / Outposts 是地理 + 監管雙重需求</strong>：美國博彩受各州監管、資料必須留在州內 → 用 Local Zones 在每個州就近部署；4G/5G 用戶投注延遲敏感 → 用 Wavelength 在電信商機房內運算；on-prem 需求 → 用 Outposts。對應 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> 的受監管雙重需求、跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> 的延遲反推 region。</li>
<li><strong>5-10x 是「同類事件中的最高倍率」</strong>：Super Bowl 是 NFL 賽季最大事件、不是常態。平日 baseline → 季後賽 2-3x → 季冠軍賽 4-5x → Super Bowl 5-10x。容量規劃要按事件級別分段、不是一律 10x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的事件型容量分級。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>AWS 案例 <em>沒有</em> 提具體 betting transaction TPS、concurrent streams、延遲分布。讀者要對 <em>策略</em> 學習、不要套用具體數字。</li>
<li>「5-10x」是 <em>峰值倍數</em>、不是 <em>peak 持續時間</em>。Super Bowl 的關鍵 30 分鐘可能 8-10x、其他 3 小時可能 3-5x。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>不同 SLO 的工作負載分開部署、不要混在同一 service</strong>：betting 跟 streaming 在 FanDuel 必然是兩個獨立微服務、各自有 dedicated infrastructure。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition、跟 <a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft</a> 同思維。</li>
<li><strong>多層 edge（Local Zone / Wavelength / Outposts）服務不同延遲需求</strong>：Local Zone 服務「州內合規」需求、Wavelength 服務「電信網內超低延遲」、Outposts 服務「on-prem 監管」需求。三者組合對應跨州博彩業務。</li>
<li><strong>事件型容量規劃分級</strong>：建立 event tier 體系（regular game / playoff / championship / super bowl），每 tier 對應不同 pre-scale 倍數。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的容量分級。</li>
</ol>
<p>跨平台等效：Azure 提供類似 stack（Stack Edge + Edge Zones + Azure for Operators）、GCP 有 Network Edge + Distributed Cloud。差異是各家 edge 覆蓋深度跟電信商合作。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他事件型峰值 → <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a>（賽事高潮 AI 預測）/ <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a></li>
<li>想設計多 SLO 對齊 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想做受監管多地區部署 → <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> + <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a></li>
<li>想做 edge / Local Zone 規劃 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想理解雙峰下 Aurora storage / replica scaling → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a> + <a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">Aurora read replica scaling</a></li>
<li>想評估 distributed SQL 在 betting 場景的 fit → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/fanduel-cloudfront-case-study/">FanDuel CloudFront Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>3.C29 WeWork：Bunny + Puma 多執行緒 channel pool</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/</guid><description>&lt;p>這個案例的核心責任是說明 AMQP client 的 connection / channel 邊界跟執行緒模型緊密耦合。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>從 Unicorn 切到 Puma 後遇到 &lt;code>ConnectionClosedError&lt;/code>、根因是快取 Bunny channel 在多執行緒間共享。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>AMQP channel 不應跨執行緒共用、改用 &lt;code>connection_pool&lt;/code> gem 管理 channel pool。揭露 AMQP 不是 stateless HTTP-style client、channel 是 statefull 物件、多 thread 模型要特別處理。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發（client library 層的 connection / channel 邊界）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://wework.github.io/ruby/rails/bunny/rabbitmq/threads/concurrency/puma/errors/2015/11/12/bunny-threads/">Bunny Threads in Puma at WeWork&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 AMQP client 的 connection / channel 邊界跟執行緒模型緊密耦合。</p>
<h2 id="觀察">觀察</h2>
<p>從 Unicorn 切到 Puma 後遇到 <code>ConnectionClosedError</code>、根因是快取 Bunny channel 在多執行緒間共享。</p>
<h2 id="判讀">判讀</h2>
<p>AMQP channel 不應跨執行緒共用、改用 <code>connection_pool</code> gem 管理 channel pool。揭露 AMQP 不是 stateless HTTP-style client、channel 是 statefull 物件、多 thread 模型要特別處理。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發（client library 層的 connection / channel 邊界）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://wework.github.io/ruby/rails/bunny/rabbitmq/threads/concurrency/puma/errors/2015/11/12/bunny-threads/">Bunny Threads in Puma at WeWork</a></li>
</ul>
]]></content:encoded></item><item><title>9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/</guid><description>&lt;p>這個案例的核心責任是說明「電信商級新串流服務」如何用雲端服務快速 launch + scale。Lemino 是 NTT DOCOMO 在 2023-04 推出的串流服務、3 個月達 5M MAU、工程工時下降 90% — 這個「不用大量工程師」的營運模式靠的是 managed services 組合、不是自建。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>NTT DOCOMO Lemino 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">Lemino Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>3 個月 MAU&lt;/td>
 &lt;td>500 萬&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同時直播頻道&lt;/td>
 &lt;td>30 channels（規劃擴到 50）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DynamoDB 請求峰值&lt;/td>
 &lt;td>tens of thousands req/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程工時下降&lt;/td>
 &lt;td>90%（vs 自建）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>啟動年份&lt;/td>
 &lt;td>2023-04&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：AWS Media Services（Elemental Link、MediaConnect、MediaLive、MediaPackage）、Amazon Aurora、Amazon DynamoDB、DynamoDB Accelerator (DAX)、Amazon OpenSearch Service。&lt;/p>
&lt;p>關鍵敘述：採用 DynamoDB 的原因 — 「connection limits became bottlenecks when experiencing a rapid increase in access」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Lemino 案例揭露三個現代串流服務啟動的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「connection limit 是 RDB 的隱性 bottleneck」是 OLTP 在 surge 下的典型問題&lt;/strong>：傳統 RDB（PostgreSQL、MySQL）每個連線吃記憶體跟 process / thread、connection pool 上限通常 1K-5K 個。當突發流量湧入、第一個爆的不是 CPU 也不是 disk、是 &lt;em>連線數量&lt;/em>。DynamoDB 的 HTTP API 模型沒有 connection state、天然解決這個問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 connection pool 議題、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 遷移動機同類。&lt;/li>
&lt;li>&lt;strong>AWS Media Services 是「電視台級」串流基礎設施&lt;/strong>：Elemental Link（encoding）、MediaConnect（transport）、MediaLive（live encoding）、MediaPackage（packaging + DRM）— 這套 stack 過往是電視台才買得起的硬體設備、AWS 把它變成 pay-per-use 服務。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 vendor-specific 串流服務評估。&lt;/li>
&lt;li>&lt;strong>90% 工程工時下降 = 走 managed 路線的真正價值&lt;/strong>：傳統電信商 launch 串流服務、要養 50-100 個 SRE + DBA + network 工程師、Lemino 用 managed 服務只需 5-10 個。差距不在「能不能 launch」、在「launch 後的維運成本」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom&lt;/a> 的同類訴求。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「tens of thousands req/sec」可能指 2 萬或 8 萬、差距 4 倍。「3 個月 5M MAU」很亮眼、但 NTT DOCOMO 自身有 8000 萬+ 電信用戶可以推、不是純自然成長。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「電信商級新串流服務」如何用雲端服務快速 launch + scale。Lemino 是 NTT DOCOMO 在 2023-04 推出的串流服務、3 個月達 5M MAU、工程工時下降 90% — 這個「不用大量工程師」的營運模式靠的是 managed services 組合、不是自建。</p>
<h2 id="觀察">觀察</h2>
<p>NTT DOCOMO Lemino 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">Lemino Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>3 個月 MAU</td>
          <td>500 萬</td>
      </tr>
      <tr>
          <td>同時直播頻道</td>
          <td>30 channels（規劃擴到 50）</td>
      </tr>
      <tr>
          <td>DynamoDB 請求峰值</td>
          <td>tens of thousands req/sec</td>
      </tr>
      <tr>
          <td>工程工時下降</td>
          <td>90%（vs 自建）</td>
      </tr>
      <tr>
          <td>啟動年份</td>
          <td>2023-04</td>
      </tr>
  </tbody>
</table>
<p>服務組合：AWS Media Services（Elemental Link、MediaConnect、MediaLive、MediaPackage）、Amazon Aurora、Amazon DynamoDB、DynamoDB Accelerator (DAX)、Amazon OpenSearch Service。</p>
<p>關鍵敘述：採用 DynamoDB 的原因 — 「connection limits became bottlenecks when experiencing a rapid increase in access」。</p>
<h2 id="判讀">判讀</h2>
<p>Lemino 案例揭露三個現代串流服務啟動的工程重點。</p>
<ol>
<li><strong>「connection limit 是 RDB 的隱性 bottleneck」是 OLTP 在 surge 下的典型問題</strong>：傳統 RDB（PostgreSQL、MySQL）每個連線吃記憶體跟 process / thread、connection pool 上限通常 1K-5K 個。當突發流量湧入、第一個爆的不是 CPU 也不是 disk、是 <em>連線數量</em>。DynamoDB 的 HTTP API 模型沒有 connection state、天然解決這個問題。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 connection pool 議題、跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 遷移動機同類。</li>
<li><strong>AWS Media Services 是「電視台級」串流基礎設施</strong>：Elemental Link（encoding）、MediaConnect（transport）、MediaLive（live encoding）、MediaPackage（packaging + DRM）— 這套 stack 過往是電視台才買得起的硬體設備、AWS 把它變成 pay-per-use 服務。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 vendor-specific 串流服務評估。</li>
<li><strong>90% 工程工時下降 = 走 managed 路線的真正價值</strong>：傳統電信商 launch 串流服務、要養 50-100 個 SRE + DBA + network 工程師、Lemino 用 managed 服務只需 5-10 個。差距不在「能不能 launch」、在「launch 後的維運成本」。對應 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> 的同類訴求。</li>
</ol>
<p>需要警惕：「tens of thousands req/sec」可能指 2 萬或 8 萬、差距 4 倍。「3 個月 5M MAU」很亮眼、但 NTT DOCOMO 自身有 8000 萬+ 電信用戶可以推、不是純自然成長。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>新串流服務優先選 DynamoDB / Cosmos DB / Bigtable 撐 metadata 層</strong>：避免 connection limit、避免 schema migration、避免 DBA 維運成本。</li>
<li><strong>AWS Media Services / GCP Media CDN / Azure Media Services 是新進入者快速 launch 的捷徑</strong>：不要重造串流 stack、直接用 vendor 提供的。</li>
<li><strong>DAX 是 DynamoDB 讀 cache 的標準解法</strong>：當讀峰值持續高（例如熱門節目首播、Hotstar 等級）、加 DAX 減少 DynamoDB 讀次數、降低成本。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a>。</li>
<li><strong>小團隊 + managed services 是電信商雲端轉型的範本</strong>：傳統電信商過去靠人海戰術、現在改靠 managed + 工程紀律。</li>
</ol>
<p>跨平台等效：GCP 提供 Media CDN + Anvato，Azure 提供 Media Services + Azure Front Door — 各家都有完整串流 stack。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他串流案例 → <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a>（live 直播）/ <a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27 Disney+</a>（VOD metadata）</li>
<li>想理解 connection limit 議題 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 遷移</a></li>
<li>想做 DAX / cache 加速 → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi ML feature store</a></li>
<li>想規劃 managed-only 串流 stack → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想做串流 metadata 的 partition / GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想評估 on-demand vs provisioned 給直播 / VOD 用 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">NTT Docomo Rebuilds Infrastructure for Lemino Streaming Service Launch</a></li>
<li><a href="https://aws.amazon.com/media/direct-to-consumer-d2c-streaming/">Direct to Consumer &amp; Streaming on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C30 Runtastic：Mirrored queue 網路負載瓶頸</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/</guid><description>&lt;p>Runtastic 的案例暴露了 RabbitMQ mirrored queue 的網路成本被嚴重低估。Mirrored queue 的可靠性提升代價是 message 在 cluster 內的網路複製量跟 mirror 數成正比，而這個成本在日常流量下可能不可見、只在壓力測試或突發流量時才暴露。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Runtastic 是 Adidas 旗下的健身追蹤平台，使用者透過 app 記錄跑步、騎車、重訓等運動資料。2020 年 COVID-19 lockdown 期間，居家運動需求爆增，平台的 concurrent user 數量在數週內翻倍。&lt;/p>
&lt;p>Runtastic 的後端架構是 microservice 架構，RabbitMQ 是服務間訊息傳遞的核心。運動資料記錄、通知推送、社交功能（好友排行、挑戰）、analytics 事件都透過 RabbitMQ 的 queue 串接。&lt;/p>
&lt;h2 id="技術挑戰mirroring-的隱藏網路成本">技術挑戰：Mirroring 的隱藏網路成本&lt;/h2>
&lt;p>Runtastic 的 RabbitMQ cluster 使用 mirrored queue（&lt;code>ha-mode: all&lt;/code>）確保訊息在 broker 故障時不遺失。Mirrored queue 把每條訊息同步複製到 cluster 中所有 node — 3 node cluster 代表每條訊息的網路傳輸量是原始大小的 3 倍。&lt;/p>
&lt;p>日常流量下，mirroring 的額外網路負載在 cluster 的頻寬容量之內，效能影響不明顯。但 lockdown 後流量翻倍時，mirroring 的網路負載跟著翻倍 — 更準確地說是翻 2×N 倍（流量 2 倍 × mirror 數 N）。&lt;/p>
&lt;p>Runtastic 的 cluster 使用了共享的網路元件（network switch / load balancer），mirroring 的流量把共享網路元件的頻寬壓到極限。表現是 broker 間的 mirroring 延遲上升 → publisher confirm 延遲上升 → producer 端的 publish latency 從毫秒跳到秒級 → 上游服務開始 timeout。&lt;/p>
&lt;p>問題的隱蔽性在於：日常監控只看 broker 的 CPU、memory、disk，沒有把 inter-node network throughput 作為關鍵指標。網路瓶頸在 broker-level metric 上的表現是「publish confirm 變慢」，容易被誤判為 broker 過載而非網路飽和。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="performance-test-定位瓶頸">Performance test 定位瓶頸&lt;/h3>
&lt;p>Runtastic 在事件發生後用 performance test 重現問題。測試揭露了 mirroring 流量跟 broker 間網路頻寬的關係 — 把 message rate 從日常的 X 推到 2X 時，inter-node traffic 超過 switch 容量，publish confirm latency 開始非線性增長。&lt;/p>
&lt;p>Performance test 的關鍵是把 inter-node network throughput 加入監控維度。RabbitMQ 3.8 的 Prometheus integration 提供了 &lt;code>rabbitmq_raft_term_total&lt;/code>、&lt;code>rabbitmq_channel_messages_published_total&lt;/code> 等指標，但 inter-node bandwidth 需要從 OS 層（&lt;code>node_exporter&lt;/code> 的 network bytes）或 switch 層取得。&lt;/p>
&lt;h3 id="調整-mirroring-配置">調整 mirroring 配置&lt;/h3>
&lt;p>Runtastic 從 &lt;code>ha-mode: all&lt;/code>（所有 node 都 mirror）調整為 &lt;code>ha-mode: exactly, ha-params: 2&lt;/code>（只 mirror 到 2 個 node）。這把每條訊息的網路複製量從 N 倍降到 2 倍，在可靠性（2 個 copy 可以容忍 1 node failure）跟網路成本之間取得平衡。&lt;/p></description><content:encoded><![CDATA[<p>Runtastic 的案例暴露了 RabbitMQ mirrored queue 的網路成本被嚴重低估。Mirrored queue 的可靠性提升代價是 message 在 cluster 內的網路複製量跟 mirror 數成正比，而這個成本在日常流量下可能不可見、只在壓力測試或突發流量時才暴露。</p>
<h2 id="業務背景">業務背景</h2>
<p>Runtastic 是 Adidas 旗下的健身追蹤平台，使用者透過 app 記錄跑步、騎車、重訓等運動資料。2020 年 COVID-19 lockdown 期間，居家運動需求爆增，平台的 concurrent user 數量在數週內翻倍。</p>
<p>Runtastic 的後端架構是 microservice 架構，RabbitMQ 是服務間訊息傳遞的核心。運動資料記錄、通知推送、社交功能（好友排行、挑戰）、analytics 事件都透過 RabbitMQ 的 queue 串接。</p>
<h2 id="技術挑戰mirroring-的隱藏網路成本">技術挑戰：Mirroring 的隱藏網路成本</h2>
<p>Runtastic 的 RabbitMQ cluster 使用 mirrored queue（<code>ha-mode: all</code>）確保訊息在 broker 故障時不遺失。Mirrored queue 把每條訊息同步複製到 cluster 中所有 node — 3 node cluster 代表每條訊息的網路傳輸量是原始大小的 3 倍。</p>
<p>日常流量下，mirroring 的額外網路負載在 cluster 的頻寬容量之內，效能影響不明顯。但 lockdown 後流量翻倍時，mirroring 的網路負載跟著翻倍 — 更準確地說是翻 2×N 倍（流量 2 倍 × mirror 數 N）。</p>
<p>Runtastic 的 cluster 使用了共享的網路元件（network switch / load balancer），mirroring 的流量把共享網路元件的頻寬壓到極限。表現是 broker 間的 mirroring 延遲上升 → publisher confirm 延遲上升 → producer 端的 publish latency 從毫秒跳到秒級 → 上游服務開始 timeout。</p>
<p>問題的隱蔽性在於：日常監控只看 broker 的 CPU、memory、disk，沒有把 inter-node network throughput 作為關鍵指標。網路瓶頸在 broker-level metric 上的表現是「publish confirm 變慢」，容易被誤判為 broker 過載而非網路飽和。</p>
<h2 id="解法">解法</h2>
<h3 id="performance-test-定位瓶頸">Performance test 定位瓶頸</h3>
<p>Runtastic 在事件發生後用 performance test 重現問題。測試揭露了 mirroring 流量跟 broker 間網路頻寬的關係 — 把 message rate 從日常的 X 推到 2X 時，inter-node traffic 超過 switch 容量，publish confirm latency 開始非線性增長。</p>
<p>Performance test 的關鍵是把 inter-node network throughput 加入監控維度。RabbitMQ 3.8 的 Prometheus integration 提供了 <code>rabbitmq_raft_term_total</code>、<code>rabbitmq_channel_messages_published_total</code> 等指標，但 inter-node bandwidth 需要從 OS 層（<code>node_exporter</code> 的 network bytes）或 switch 層取得。</p>
<h3 id="調整-mirroring-配置">調整 mirroring 配置</h3>
<p>Runtastic 從 <code>ha-mode: all</code>（所有 node 都 mirror）調整為 <code>ha-mode: exactly, ha-params: 2</code>（只 mirror 到 2 個 node）。這把每條訊息的網路複製量從 N 倍降到 2 倍，在可靠性（2 個 copy 可以容忍 1 node failure）跟網路成本之間取得平衡。</p>
<p>對可靠性要求最高的 queue（交易相關），維持 <code>ha-mode: all</code> 但把這些 queue 移到頻寬更高的專屬 network segment。</p>
<h3 id="遷移到-quorum-queue-的動機">遷移到 Quorum queue 的動機</h3>
<p>Mirrored queue 的另一個問題是同步機制 — 新 mirror 加入時需要全量同步（sync），sync 期間 queue 可能暫停接受新訊息。RabbitMQ 3.8 引入的 Quorum queue 用 Raft consensus 取代 mirrored queue 的 GM（Guaranteed Multicast），在網路效率跟故障恢復上都有改進。</p>
<p>Runtastic 的案例是「為什麼應該評估從 mirrored queue 遷到 quorum queue」的典型動機 — mirrored queue 的網路成本跟同步行為在規模化時成為瓶頸。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>ha-mode: all</th>
          <th>ha-mode: exactly 2</th>
          <th>Quorum queue</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>網路成本</td>
          <td>每條訊息 × N node</td>
          <td>每條訊息 × 2 node</td>
          <td>每條訊息 × majority</td>
      </tr>
      <tr>
          <td>可容忍的故障</td>
          <td>N-1 node failure</td>
          <td>1 node failure</td>
          <td>minority node failure</td>
      </tr>
      <tr>
          <td>新 node 加入</td>
          <td>全量同步（可能暫停 queue）</td>
          <td>全量同步（影響面小）</td>
          <td>Raft log replay（漸進）</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>小 cluster、低流量</td>
          <td>中 cluster、中流量</td>
          <td>中大 cluster、推薦路徑</td>
      </tr>
  </tbody>
</table>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 的 replication 跟 network 成本的關係</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a>：mirrored queue vs quorum queue 的詳細比較</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/queue-types-classic-quorum-stream/" data-link-title="RabbitMQ Queue Type 選型：Classic、Quorum、Stream 的責任邊界與容量取捨" data-link-desc="RabbitMQ 3.x 三種 queue type 的選型 deep article — classic queue（mirrored 已 deprecated）、quorum queue（Raft 一致性、取代 mirrored）、stream（3.9&#43; append-only log、可重複消費）。涵蓋三種模型在 throughput / retention / replay / 記憶體成本的判讀、宣告語意差異（實機驗證）、4 個 production 故障演練（mirrored 網路放大 / quorum loss / stream retention 超量 / classic→quorum in-flight message），與容量規劃。">RabbitMQ queue types</a>：Classic / Mirrored / Quorum / Stream 四種 queue type 的取捨</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a>：broker 的 inter-node 網路作為 pipeline 健康指標</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>RabbitMQ cluster 使用 <code>ha-mode: all</code> 且 node 數量 &gt; 3</li>
<li>Publish confirm latency 在流量上升時非線性增長</li>
<li>Broker 的 CPU / memory / disk 指標正常但 publish 變慢</li>
<li>Broker 間的 network traffic 佔比超過 cluster 總頻寬的 50%</li>
<li>新 mirror 加入時 queue 出現暫停或大量延遲</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://seventhstate.io/portfolio/portfolio-runtastic/">Runtastic RabbitMQ Performance Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/</guid><description>&lt;p>這個案例的核心責任是填補 Azure data-architecture 維度缺口、並提供「MongoDB → Cosmos DB」這個跨產品遷移的官方範本。Microsoft 365 是全球最大 SaaS 之一（月活十億級）、其使用分析平台的容量需求是 planet-scale。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Microsoft 365 在 Cosmos DB 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB&lt;/a>）：&lt;/p>
&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>Microsoft 365 全球用戶（十億級 MAU）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工作負載&lt;/td>
 &lt;td>使用分析（usage analytics）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷出技術&lt;/td>
 &lt;td>MongoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷入技術&lt;/td>
 &lt;td>Azure Cosmos DB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移動機&lt;/td>
 &lt;td>「globally-distributed, multi-model」「virtually unlimited elastic scalability」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「The team decided to replace MongoDB with Azure Cosmos DB, a fully managed globally-distributed, multi-model database service designed for global distribution and virtually unlimited elastic scalability.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Microsoft 365 案例揭露三個全球 SaaS 分析平台的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>MongoDB → Cosmos DB 是「相容 API + 升級擴展性」的遷移路徑&lt;/strong>：Cosmos DB 提供 MongoDB API 相容、應用層程式幾乎不用改、但底層儲存改用 Cosmos DB 的分散式架構。這層遷移成本遠低於改寫 application 到 native Cosmos DB SQL API、適合大規模既有系統。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a>、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 形成對照。&lt;/li>
&lt;li>&lt;strong>分析平台 vs 交易平台的 DB 取捨不同&lt;/strong>：交易平台優先 latency + consistency（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a>）、分析平台優先 throughput + global distribution + cost。Cosmos DB 5 個 consistency level 讓分析場景可以選 weakest（eventual / session），換最大 throughput。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 同思維。&lt;/li>
&lt;li>&lt;strong>Microsoft 自家產品 dogfood Cosmos DB&lt;/strong>：跟 Amazon Prime Day 用自家 DynamoDB（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1&lt;/a>）、Google 自家用 Spanner（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10&lt;/a>）一樣 — 雲商旗艦 DB 都會用在自家旗艦產品。讀此類 dogfood 案例的權重應該高、因為「雲商自己賭身家」。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是填補 Azure data-architecture 維度缺口、並提供「MongoDB → Cosmos DB」這個跨產品遷移的官方範本。Microsoft 365 是全球最大 SaaS 之一（月活十億級）、其使用分析平台的容量需求是 planet-scale。</p>
<h2 id="觀察">觀察</h2>
<p>Microsoft 365 在 Cosmos DB 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶規模</td>
          <td>Microsoft 365 全球用戶（十億級 MAU）</td>
      </tr>
      <tr>
          <td>工作負載</td>
          <td>使用分析（usage analytics）</td>
      </tr>
      <tr>
          <td>遷出技術</td>
          <td>MongoDB</td>
      </tr>
      <tr>
          <td>遷入技術</td>
          <td>Azure Cosmos DB</td>
      </tr>
      <tr>
          <td>遷移動機</td>
          <td>「globally-distributed, multi-model」「virtually unlimited elastic scalability」</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「The team decided to replace MongoDB with Azure Cosmos DB, a fully managed globally-distributed, multi-model database service designed for global distribution and virtually unlimited elastic scalability.」</p>
<h2 id="判讀">判讀</h2>
<p>Microsoft 365 案例揭露三個全球 SaaS 分析平台的工程重點。</p>
<ol>
<li><strong>MongoDB → Cosmos DB 是「相容 API + 升級擴展性」的遷移路徑</strong>：Cosmos DB 提供 MongoDB API 相容、應用層程式幾乎不用改、但底層儲存改用 Cosmos DB 的分散式架構。這層遷移成本遠低於改寫 application 到 native Cosmos DB SQL API、適合大規模既有系統。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>、跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 形成對照。</li>
<li><strong>分析平台 vs 交易平台的 DB 取捨不同</strong>：交易平台優先 latency + consistency（<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a>）、分析平台優先 throughput + global distribution + cost。Cosmos DB 5 個 consistency level 讓分析場景可以選 weakest（eventual / session），換最大 throughput。對應 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 同思維。</li>
<li><strong>Microsoft 自家產品 dogfood Cosmos DB</strong>：跟 Amazon Prime Day 用自家 DynamoDB（<a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1</a>）、Google 自家用 Spanner（<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a>）一樣 — 雲商旗艦 DB 都會用在自家旗艦產品。讀此類 dogfood 案例的權重應該高、因為「雲商自己賭身家」。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>案例 <em>沒有</em> 提具體 throughput、latency、cost 數字。Microsoft 內部數字通常不公開、跟 AWS / GCP 案例的數字密度差很多。</li>
<li>「MongoDB 不夠用」是行銷話術。實際是 <em>MongoDB 在某些 workload pattern 下不夠用</em>、不是普遍結論。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>MongoDB-compatible Cosmos DB 是大規模遷移的捷徑</strong>：應用層改動少、底層擴展性升級。但要驗證 <em>特定 query pattern</em> 在兩邊行為一致。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的 dual-write 驗證。</li>
<li><strong>分析平台用 weakest acceptable consistency</strong>：session consistency 或 eventual consistency 通常夠用、能換到 3-10x throughput。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>dogfood 是 vendor selection 的重要訊號</strong>：vendor 自家是否用在 production-critical workload、能告訴你「他們對自己服務的信任度」。</li>
<li><strong>Multi-model 是 Cosmos DB 的差異化價值</strong>：同一個服務可以用 SQL API / MongoDB API / Cassandra API / Gremlin / Table API、避免多個 DB 服務並存。</li>
</ol>
<p>跨平台等效：AWS DynamoDB（KV）+ DocumentDB（MongoDB-compatible）、GCP Firestore（document）+ Spanner（SQL）+ Bigtable（KV）— 各家用不同產品覆蓋 multi-model、Cosmos DB 是少數「單一產品支援多 model」。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 Cosmos DB 案例 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a></li>
<li>對照其他 dogfood 案例 → <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做 MongoDB-compatible 遷移 → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解 multi-model 取捨 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想對比 Cosmos DB MongoDB API vs SQL API 的選型 → <a href="/blog/backend/01-database/vendors/cosmosdb/mongodb-api-vs-sql-api/" data-link-title="Cosmos DB MongoDB API vs SQL API：遷移路徑、dogfood signal、multi-model、跨雲 hedging" data-link-desc="從『MongoDB API 跟 SQL API 哪個快』推進到 vendor selection 的四層問題：三型遷移路徑、dogfood signal 怎麼讀、multi-model 差異化、跨雲 hedging — 從 Microsoft 365 dogfood 案例切入">Cosmos DB MongoDB API vs SQL API</a></li>
<li>想做 RU 成本模型與容量 sizing → <a href="/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/" data-link-title="Cosmos DB RU/s 成本模型 &#43; 容量規劃：RU 思維、payload、index、provisioned vs autoscale vs serverless" data-link-desc="從 CPU&#43;IOPS 思維轉到 RU 思維的學習曲線、依負載形狀選容量模式、payload &#43; index policy 對 RU 的影響、autoscale reactive 限制 — 從 ASOS Black Friday &#43; Minecraft Earth 1M RU/s 壓測切入">Cosmos DB RU 成本模型</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">A technical overview of Azure Cosmos DB</a></li>
</ul>
]]></content:encoded></item><item><title>3.C31 Mozilla Pulse：命名前綴 + ACL 取代 vhost 多租戶</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/</guid><description>&lt;p>這個案例的核心責任是說明多租戶隔離可用「ACL + naming convention」取代 vhost、適合社群協作場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pulse 是 Mozilla 自動化 / 基礎設施工具間的 managed RabbitMQ cluster、用 AMQP 0-9-1 + RabbitMQ 擴充、由 CloudAMQP 託管於 pulse.mozilla.org:5671（AMQP over TLS）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>技術上不需 vhost、改用權限限制 + 命名前綴（&lt;code>exchange/&amp;lt;username&amp;gt;/*&lt;/code>、&lt;code>queue/&amp;lt;username&amp;gt;/*&lt;/code>）做隔離。PulseGuardian 跑在 Heroku 管理使用者 / queue / exchange。揭露多租戶隔離不一定要 vhost、權限粒度可以拉到 resource naming 層。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：多 vhost + 多租戶（反向案例：用 ACL + naming 取代 vhost）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &amp;#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg vhost 多租戶&lt;/a>（對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://wiki.mozilla.org/Auto-tools/Projects/Pulse">Mozilla Pulse Wiki&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pulse.mozilla.org/api/">Pulse API&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明多租戶隔離可用「ACL + naming convention」取代 vhost、適合社群協作場景。</p>
<h2 id="觀察">觀察</h2>
<p>Pulse 是 Mozilla 自動化 / 基礎設施工具間的 managed RabbitMQ cluster、用 AMQP 0-9-1 + RabbitMQ 擴充、由 CloudAMQP 託管於 pulse.mozilla.org:5671（AMQP over TLS）。</p>
<h2 id="判讀">判讀</h2>
<p>技術上不需 vhost、改用權限限制 + 命名前綴（<code>exchange/&lt;username&gt;/*</code>、<code>queue/&lt;username&gt;/*</code>）做隔離。PulseGuardian 跑在 Heroku 管理使用者 / queue / exchange。揭露多租戶隔離不一定要 vhost、權限粒度可以拉到 resource naming 層。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：多 vhost + 多租戶（反向案例：用 ACL + naming 取代 vhost）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg vhost 多租戶</a>（對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://wiki.mozilla.org/Auto-tools/Projects/Pulse">Mozilla Pulse Wiki</a></li>
<li><a href="https://pulse.mozilla.org/api/">Pulse API</a></li>
</ul>
]]></content:encoded></item><item><title>9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/</guid><description>&lt;p>這個案例的核心責任是補強 GCP 案例庫的「商業應用」深度、並提供拉丁美洲電商規模對標。Mercado Libre 是拉丁美洲最大電商（市值 600 億美金級）、業務涵蓋 18 個國家、是區域型平台的容量規劃範本。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Mercado Libre 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story&lt;/a>）：&lt;/p>
&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>1 億&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品數&lt;/td>
 &lt;td>1.5 億（3 個試點國家）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務影響&lt;/td>
 &lt;td>數百萬美金 incremental revenue（Vertex AI Search）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要 GCP 服務&lt;/td>
 &lt;td>Vertex AI Search、BigQuery&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料即時性&lt;/td>
 &lt;td>near real-time&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>拉丁美洲&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵能力：「Vertex AI Search across 150 million items in three pilot countries that is helping its 100 million customers find the products they love faster」、「BigQuery to design a robust data architecture that ensures the availability of data in near real-time」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Mercado Libre 揭露三個區域電商容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>區域電商 ≠ 全球電商&lt;/strong>：拉丁美洲 18 個國家、各自有獨立貨幣、稅務、物流、合規規則。容量規劃單位通常是「per country」、不是「per region」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered&lt;/a> 的市場分割、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow&lt;/a> 的跨國平台對照。&lt;/li>
&lt;li>&lt;strong>Vertex AI Search = 「搜尋」當作 ML 服務、不是 Elasticsearch&lt;/strong>：傳統電商搜尋靠 Elasticsearch / OpenSearch + 自訓 ranker、Mercado Libre 用 vendor managed Vertex AI Search、把「商品搜尋 + 推薦排序」當作 ML 黑盒。這個取捨用「不可調參」換「快速上線」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 build vs buy、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify&lt;/a> 的 managed 轉向同類思維。&lt;/li>
&lt;li>&lt;strong>「數百萬美金 incremental revenue」是 ML 容量規劃的真實 ROI&lt;/strong>：搜尋改善 → 轉換率 → 訂單 → 收入、ML 投資的 cost 才能合理化。容量規劃不只看「能撐多大流量」、也要看「擴容能否帶業務 ROI」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 GCP 案例庫的「商業應用」深度、並提供拉丁美洲電商規模對標。Mercado Libre 是拉丁美洲最大電商（市值 600 億美金級）、業務涵蓋 18 個國家、是區域型平台的容量規劃範本。</p>
<h2 id="觀察">觀察</h2>
<p>Mercado Libre 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>1 億</td>
      </tr>
      <tr>
          <td>商品數</td>
          <td>1.5 億（3 個試點國家）</td>
      </tr>
      <tr>
          <td>業務影響</td>
          <td>數百萬美金 incremental revenue（Vertex AI Search）</td>
      </tr>
      <tr>
          <td>主要 GCP 服務</td>
          <td>Vertex AI Search、BigQuery</td>
      </tr>
      <tr>
          <td>資料即時性</td>
          <td>near real-time</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>拉丁美洲</td>
      </tr>
  </tbody>
</table>
<p>關鍵能力：「Vertex AI Search across 150 million items in three pilot countries that is helping its 100 million customers find the products they love faster」、「BigQuery to design a robust data architecture that ensures the availability of data in near real-time」。</p>
<h2 id="判讀">判讀</h2>
<p>Mercado Libre 揭露三個區域電商容量規劃重點。</p>
<ol>
<li><strong>區域電商 ≠ 全球電商</strong>：拉丁美洲 18 個國家、各自有獨立貨幣、稅務、物流、合規規則。容量規劃單位通常是「per country」、不是「per region」。對應 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> 的市場分割、跟 <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a> 的跨國平台對照。</li>
<li><strong>Vertex AI Search = 「搜尋」當作 ML 服務、不是 Elasticsearch</strong>：傳統電商搜尋靠 Elasticsearch / OpenSearch + 自訓 ranker、Mercado Libre 用 vendor managed Vertex AI Search、把「商品搜尋 + 推薦排序」當作 ML 黑盒。這個取捨用「不可調參」換「快速上線」。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 build vs buy、跟 <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> 的 managed 轉向同類思維。</li>
<li><strong>「數百萬美金 incremental revenue」是 ML 容量規劃的真實 ROI</strong>：搜尋改善 → 轉換率 → 訂單 → 收入、ML 投資的 cost 才能合理化。容量規劃不只看「能撐多大流量」、也要看「擴容能否帶業務 ROI」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的成本工程化。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「1.5 億商品 in 3 pilot countries」是 <em>試點規模</em>、不是全平台。全平台商品總數應該更大、但案例沒揭露。</li>
<li>BigQuery「near real-time」沒指明 latency（秒級、分鐘級）。BigQuery 傳統是 minutes-level、不是 sub-second、對「即時」的定義要謹慎。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>區域電商的容量規劃是「per country × peak_factor」</strong>：不是「per region」聚合、要按國家分別規劃。每個國家自己的 Black Friday / Cyber Monday / 雙 11 / 6.18 等本地大促時間都不同。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>「商品搜尋」適合用 managed AI search</strong>：除非有自家強大的 ML team + 大量訓練資料、否則 Vertex AI Search / OpenSearch Service 等 managed 比自建 ranker 划算。</li>
<li><strong>BigQuery 是 LatAm / 新興市場數據平台的標配</strong>：能處理 PB 級資料、無需 cluster 管理、適合中等工程資源的團隊。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 data 平台選型、跟 <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a> 的 Redshift + Athena 對照。</li>
<li><strong>ML ROI 直接 ＝ 業務指標</strong>：transaction conversion rate、AOV、recommendation CTR 都是 ML 容量規劃的下游 KPI。</li>
</ol>
<p>跨平台等效：AWS Personalize + Redshift + Glue、Azure AI Search + Synapse 都是對等候選。差異是 vendor 整合度跟模型的可調參空間。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模電商 → <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair burst</a></li>
<li>想規劃跨國容量 → <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> + <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a></li>
<li>想做 ML feature serving → <a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi ML feature store</a></li>
<li>想做 build vs buy 決策 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story</a></li>
<li><a href="https://cloud.google.com/blog/products/data-analytics/how-mercado-libre-uses-real-time-analytics-for-on-time-delivery">How Mercado Libre uses real-time analytics for on-time delivery</a></li>
</ul>
]]></content:encoded></item><item><title>Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/</guid><description>&lt;p>Amazon 可靠性設計的核心責任是把失效影響限制在局部邊界。當系統採用多租戶與大規模共享資源，隔離策略必須先於恢復策略被定義，否則任何回復動作都會變成全域風險。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>多租戶服務常見的放大路徑是「單租戶異常 → 共享資源飽和 → 全域退化」。若路由與容量都沒有明確邊界，團隊只能在事故後做整體降載，代價高且恢復慢。&lt;/p>
&lt;p>cell-based architecture 與 shuffle sharding 提供的是前置結構：先限制擴散，再談恢復。&lt;/p>
&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>Cell 邊界&lt;/td>
 &lt;td>一個失效最多影響到哪裡&lt;/td>
 &lt;td>局部故障域&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shuffle sharding&lt;/td>
 &lt;td>熱點租戶如何避免重疊影響&lt;/td>
 &lt;td>隨機子集合隔離&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Static stability&lt;/td>
 &lt;td>控制面失效時資料面如何維持&lt;/td>
 &lt;td>降級持續服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Constant work&lt;/td>
 &lt;td>失敗模式下是否維持固定工作量&lt;/td>
 &lt;td>防放大設計&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這組機制讓恢復策略從「全域搶救」轉為「分批收斂」。在可用性與成本取捨上，局部隔離通常比全域冗餘更可持續。&lt;/p>
&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>shard contention&lt;/td>
 &lt;td>熱點是否跨 shard 擴散&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cell error isolation ratio&lt;/td>
 &lt;td>錯誤是否被限制在局部&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>recovery batch completion&lt;/td>
 &lt;td>分批恢復是否可預測&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>control-plane dependency lag&lt;/td>
 &lt;td>控制面異常是否拖累資料面&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把 sharding 當成純擴容手段會忽略隔離責任。當分片策略只服務容量，沒有對齊失效邊界，事故時仍會看到跨租戶共振。真正的設計重點是「隔離優先，擴容其次」。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>要把案例轉成可執行設計，先定義 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a> 的依賴預算與共享邊界，再在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a> 驗證局部化假設。事故時的分批回復流程回到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Amazon 可靠性設計的核心責任是把失效影響限制在局部邊界。當系統採用多租戶與大規模共享資源，隔離策略必須先於恢復策略被定義，否則任何回復動作都會變成全域風險。</p>
<h2 id="問題場景">問題場景</h2>
<p>多租戶服務常見的放大路徑是「單租戶異常 → 共享資源飽和 → 全域退化」。若路由與容量都沒有明確邊界，團隊只能在事故後做整體降載，代價高且恢復慢。</p>
<p>cell-based architecture 與 shuffle sharding 提供的是前置結構：先限制擴散，再談恢復。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cell 邊界</td>
          <td>一個失效最多影響到哪裡</td>
          <td>局部故障域</td>
      </tr>
      <tr>
          <td>Shuffle sharding</td>
          <td>熱點租戶如何避免重疊影響</td>
          <td>隨機子集合隔離</td>
      </tr>
      <tr>
          <td>Static stability</td>
          <td>控制面失效時資料面如何維持</td>
          <td>降級持續服務</td>
      </tr>
      <tr>
          <td>Constant work</td>
          <td>失敗模式下是否維持固定工作量</td>
          <td>防放大設計</td>
      </tr>
  </tbody>
</table>
<p>這組機制讓恢復策略從「全域搶救」轉為「分批收斂」。在可用性與成本取捨上，局部隔離通常比全域冗餘更可持續。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>shard contention</td>
          <td>熱點是否跨 shard 擴散</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>cell error isolation ratio</td>
          <td>錯誤是否被限制在局部</td>
          <td><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a></td>
      </tr>
      <tr>
          <td>recovery batch completion</td>
          <td>分批恢復是否可預測</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>control-plane dependency lag</td>
          <td>控制面異常是否拖累資料面</td>
          <td><a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把 sharding 當成純擴容手段會忽略隔離責任。當分片策略只服務容量，沒有對齊失效邊界，事故時仍會看到跨租戶共振。真正的設計重點是「隔離優先，擴容其次」。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>要把案例轉成可執行設計，先定義 <a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a> 的依賴預算與共享邊界，再在 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a> 驗證局部化假設。事故時的分批回復流程回到 <a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a>。</p>
]]></content:encoded></item><item><title>Discord：Gateway 容量事件與恢復節奏</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/</guid><description>&lt;p>這起案例的核心責任是把長連線流量恢復做成可分批節奏。容量事件若直接全量回復，容易觸發二次擁塞。&lt;/p>
&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>gateway saturation&lt;/td>
 &lt;td>是否超出穩態邊界&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>reconnect queue growth&lt;/td>
 &lt;td>回復是否放大壓力&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>region imbalance&lt;/td>
 &lt;td>影響是否偏斜&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「長連線回復節奏」不能跨過穩態容量。主要風險是全量 reconnect 直接壓垮 gateway，讓恢復動作本身成為二次事故來源。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先定義分批回復門檻，再在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14&lt;/a> 固化協調規則，並回寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a> 的穩態門檻。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是把長連線流量恢復做成可分批節奏。容量事件若直接全量回復，容易觸發二次擁塞。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>gateway saturation</td>
          <td>是否超出穩態邊界</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>reconnect queue growth</td>
          <td>回復是否放大壓力</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>region imbalance</td>
          <td>影響是否偏斜</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「長連線回復節奏」不能跨過穩態容量。主要風險是全量 reconnect 直接壓垮 gateway，讓恢復動作本身成為二次事故來源。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先定義分批回復門檻，再在 <a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a> 固化協調規則，並回寫 <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a> 的穩態門檻。</p>
]]></content:encoded></item><item><title>LinkedIn：Capacity Headroom 與 On-call 分層</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/</guid><description>&lt;p>LinkedIn 案例的核心責任是讓容量治理與 on-call 分工一起運作。高流量服務的穩定性不只靠擴容，還靠清楚的接手邏輯。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>當流量逼近上限時，技術瓶頸與協作瓶頸會同時出現。若只有容量模型，沒有分層值班，恢復節奏仍會失控。&lt;/p>
&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>Headroom 預算&lt;/td>
 &lt;td>何時進入風險區&lt;/td>
 &lt;td>擴容與限流門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Primary/Secondary/SME&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;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>replication latency&lt;/td>
 &lt;td>是否接近容量邊界&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>on-call handoff latency&lt;/td>
 &lt;td>分層交接是否順暢&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&amp;#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>load-test drift&lt;/td>
 &lt;td>模型與真實壓力是否偏移&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>把容量假設寫進 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>，再把交接規則對齊 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>LinkedIn 案例的核心責任是讓容量治理與 on-call 分工一起運作。高流量服務的穩定性不只靠擴容，還靠清楚的接手邏輯。</p>
<h2 id="問題場景">問題場景</h2>
<p>當流量逼近上限時，技術瓶頸與協作瓶頸會同時出現。若只有容量模型，沒有分層值班，恢復節奏仍會失控。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Headroom 預算</td>
          <td>何時進入風險區</td>
          <td>擴容與限流門檻</td>
      </tr>
      <tr>
          <td>Primary/Secondary/SME</td>
          <td>何時由誰接手</td>
          <td>升級路徑</td>
      </tr>
      <tr>
          <td>自動化壓測</td>
          <td>模型是否貼近現況</td>
          <td>驗證循環</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>replication latency</td>
          <td>是否接近容量邊界</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td>on-call handoff latency</td>
          <td>分層交接是否順暢</td>
          <td><a href="/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12</a></td>
      </tr>
      <tr>
          <td>load-test drift</td>
          <td>模型與真實壓力是否偏移</td>
          <td><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>把容量假設寫進 <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a>，再把交接規則對齊 <a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2</a>。</p>
]]></content:encoded></item><item><title>Amazon：Static Stability 與 Constant Work Pattern</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/static-stability/" data-link-title="Static Stability" data-link-desc="控制面失效時資料面用快取的已知好配置繼續服務的設計模式">Static stability&lt;/a> 的責任是讓資料面在控制面故障時仍能維持服務。Constant work pattern 的責任是讓系統在失敗模式下的工作量與正常時相同。兩者共同保護系統在最需要穩定時不會因為自救動作而崩潰。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>控制面管理路由、配置推送、服務發現與 auto-scaling。當控制面本身失效，依賴控制面的資料面會同時進入未知狀態。最危險的放大路徑是：控制面掛掉後，資料面節點同時嘗試重新連線或重新取得配置，retry storm 把殘餘容量耗盡，資料面跟著崩潰。&lt;/p>
&lt;p>這個問題在大規模平台上尤其嚴重。節點越多，控制面恢復時的同時 pull 量越大，恢復本身就會變成新的負載來源。&lt;/p>
&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>Static stability&lt;/td>
 &lt;td>控制面不可用時資料面能否繼續服務&lt;/td>
 &lt;td>快取的配置必須是完整可用狀態，不能是 partial update&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Constant work&lt;/td>
 &lt;td>失敗模式下的系統工作量是否跟正常時相同&lt;/td>
 &lt;td>push-based 優於 pull-based：定時推全量，不靠拉取&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pre-computed fallback&lt;/td>
 &lt;td>控制面失效時是否有不需要即時計算的備援路徑&lt;/td>
 &lt;td>fallback 路徑預先建好，切換動作本身不依賴控制面&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Static stability 的實作核心是讓每個資料面節點持有控制面最後已知的好配置。當控制面恢復通訊時，節點用最新配置更新快取；當通訊中斷時，節點用快取繼續服務。這個設計要求配置快取是完整的（能獨立驅動服務），而不是差分的（需要跟控制面合併才能用）。&lt;/p>
&lt;p>Constant work pattern 的核心是讓系統無論在正常或故障狀態下都執行相同的工作量。push-based config distribution 在每個週期推送全量配置給所有節點，不管配置是否有變更。這樣在控制面恢復時，不會因為所有節點同時 pull 而產生 thundering herd。相比之下，pull-based 在正常時流量低，但控制面恢復瞬間流量暴增。&lt;/p>
&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>control-plane health&lt;/td>
 &lt;td>控制面是否可用、是否在退化中&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cache staleness&lt;/td>
 &lt;td>快取配置距離最後更新多久&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>recovery work amplification&lt;/td>
 &lt;td>恢復過程中負載是否比正常時更高&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>data-plane autonomous duration&lt;/td>
 &lt;td>資料面在無控制面時能獨立運作多久&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>cache staleness 是 static stability 最關鍵的健康指標。當快取新鮮度超過預設門檻（取決於配置變更頻率），資料面仍能服務，但服務行為可能與最新意圖不一致。這個門檻決定了 degraded mode 的可接受時間窗。&lt;/p>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把控制面失效視為低概率事件而不做 static stability 設計，會在真正發生時暴露循環依賴。Meta 2021-10 事故中，BGP 配置變更導致控制面與資料面共用的網路路徑同時失效，而恢復工具本身也依賴這條路徑，恢復動作陷入循環等待。這個案例說明 static stability 的價值在事前設計，而非事後補救。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal&lt;/a>：static stability 讓資料面在災難期間自主運作，是 DR by design&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget&lt;/a>：控制面是最高風險的內部依賴，budget 設計要先處理控制面失效&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition&lt;/a>：degraded mode 下的穩態需要包含「控制面不可用但資料面仍服務」的定義&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/builders-library/static-stability-using-availability-zones/">Static stability using Availability Zones&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/">Avoiding insurmountable queue backlogs&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p><a href="/blog/backend/knowledge-cards/static-stability/" data-link-title="Static Stability" data-link-desc="控制面失效時資料面用快取的已知好配置繼續服務的設計模式">Static stability</a> 的責任是讓資料面在控制面故障時仍能維持服務。Constant work pattern 的責任是讓系統在失敗模式下的工作量與正常時相同。兩者共同保護系統在最需要穩定時不會因為自救動作而崩潰。</p>
<h2 id="問題場景">問題場景</h2>
<p>控制面管理路由、配置推送、服務發現與 auto-scaling。當控制面本身失效，依賴控制面的資料面會同時進入未知狀態。最危險的放大路徑是：控制面掛掉後，資料面節點同時嘗試重新連線或重新取得配置，retry storm 把殘餘容量耗盡，資料面跟著崩潰。</p>
<p>這個問題在大規模平台上尤其嚴重。節點越多，控制面恢復時的同時 pull 量越大，恢復本身就會變成新的負載來源。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>設計約束</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Static stability</td>
          <td>控制面不可用時資料面能否繼續服務</td>
          <td>快取的配置必須是完整可用狀態，不能是 partial update</td>
      </tr>
      <tr>
          <td>Constant work</td>
          <td>失敗模式下的系統工作量是否跟正常時相同</td>
          <td>push-based 優於 pull-based：定時推全量，不靠拉取</td>
      </tr>
      <tr>
          <td>Pre-computed fallback</td>
          <td>控制面失效時是否有不需要即時計算的備援路徑</td>
          <td>fallback 路徑預先建好，切換動作本身不依賴控制面</td>
      </tr>
  </tbody>
</table>
<p>Static stability 的實作核心是讓每個資料面節點持有控制面最後已知的好配置。當控制面恢復通訊時，節點用最新配置更新快取；當通訊中斷時，節點用快取繼續服務。這個設計要求配置快取是完整的（能獨立驅動服務），而不是差分的（需要跟控制面合併才能用）。</p>
<p>Constant work pattern 的核心是讓系統無論在正常或故障狀態下都執行相同的工作量。push-based config distribution 在每個週期推送全量配置給所有節點，不管配置是否有變更。這樣在控制面恢復時，不會因為所有節點同時 pull 而產生 thundering herd。相比之下，pull-based 在正常時流量低，但控制面恢復瞬間流量暴增。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>control-plane health</td>
          <td>控制面是否可用、是否在退化中</td>
          <td><a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13</a></td>
      </tr>
      <tr>
          <td>cache staleness</td>
          <td>快取配置距離最後更新多久</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>recovery work amplification</td>
          <td>恢復過程中負載是否比正常時更高</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>data-plane autonomous duration</td>
          <td>資料面在無控制面時能獨立運作多久</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a></td>
      </tr>
  </tbody>
</table>
<p>cache staleness 是 static stability 最關鍵的健康指標。當快取新鮮度超過預設門檻（取決於配置變更頻率），資料面仍能服務，但服務行為可能與最新意圖不一致。這個門檻決定了 degraded mode 的可接受時間窗。</p>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把控制面失效視為低概率事件而不做 static stability 設計，會在真正發生時暴露循環依賴。Meta 2021-10 事故中，BGP 配置變更導致控制面與資料面共用的網路路徑同時失效，而恢復工具本身也依賴這條路徑，恢復動作陷入循環等待。這個案例說明 static stability 的價值在事前設計，而非事後補救。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal</a>：static stability 讓資料面在災難期間自主運作，是 DR by design</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：控制面是最高風險的內部依賴，budget 設計要先處理控制面失效</li>
<li><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition</a>：degraded mode 下的穩態需要包含「控制面不可用但資料面仍服務」的定義</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/builders-library/static-stability-using-availability-zones/">Static stability using Availability Zones</a></li>
<li><a href="https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/">Avoiding insurmountable queue backlogs</a></li>
</ul>
]]></content:encoded></item><item><title>Honeycomb：Production Excellence 與 Test in Production</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/</guid><description>&lt;p>Honeycomb 團隊是 test in production 理念的主要推動者之一。Production excellence 的核心責任是把 production 觀測能力提升到可以安全驗證變更的水準。當觀測能力足夠細緻，團隊可以在真實流量下驗證行為，降低對 staging 環境的依賴。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>Staging 跟 production 之間的差異是結構性的 — 資料量不同、流量模式不同、依賴行為不同、cache 狀態不同。團隊投入大量精力維護 staging parity，但差異仍然存在，staging 通過但 production 失敗的事故反覆出現。&lt;/p>
&lt;p>Honeycomb 提出的替代思路是：與其追求 staging 完美複製 production，不如提升 production 的觀測能力，讓驗證可以安全地在 production 執行。這個思路的前提是三個能力同時到位：high-cardinality observability 能即時看見異常、feature flag 能控制變更的可見範圍、automated rollback 能在問題擴大前收回變更。&lt;/p>
&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>Observability readiness&lt;/td>
 &lt;td>觀測能否按 tenant / path / feature 切分&lt;/td>
 &lt;td>high-cardinality trace / structured event&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Feature flag safety&lt;/td>
 &lt;td>變更可見範圍是否可控&lt;/td>
 &lt;td>dark launch + kill switch&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Progressive validation&lt;/td>
 &lt;td>每一步放量是否有即時回饋&lt;/td>
 &lt;td>canary → observe → expand 循環&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rollback readiness&lt;/td>
 &lt;td>異常出現時能否自動收回&lt;/td>
 &lt;td>automated rollback on anomaly trigger&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Observability readiness 是整個流程的前提。high-cardinality tracing 讓團隊可以按 tenant、feature flag 狀態、request path 等維度切分觀測資料，在問題只影響少量使用者時就被發現。若觀測只有聚合指標（平均 latency、總 error rate），異常會被稀釋到看不見，等到聚合指標也惡化時影響已經擴大。&lt;/p>
&lt;p>Feature flag safety 控制變更的 blast radius。dark launch 讓新邏輯在 production 執行但結果不對外可見，用來驗證效能與正確性。kill switch 讓團隊在異常出現時立即關閉新邏輯，不需要等 redeploy。&lt;/p>
&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>trace cardinality coverage&lt;/td>
 &lt;td>觀測維度是否足以切分異常&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>flag rollout anomaly&lt;/td>
 &lt;td>新 flag 開啟後行為是否偏離&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>production validation pass&lt;/td>
 &lt;td>驗證結果是否支持繼續放量&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollback trigger count&lt;/td>
 &lt;td>自動回退是否被觸發&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把 test in production 當成「跳過 staging 測試」的簡稱會帶來嚴重風險。test in production 的安全性建立在三個前提上：觀測能力能即時看見異常、feature flag 能控制影響範圍、rollback 能在秒級生效。缺少任何一個前提就直接在 production 測試，只是把風險從 staging 搬到 production，而且 production 的失敗成本更高。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/environment-parity/" data-link-title="6.15 Environment Parity 與漂移控制" data-link-desc="把 staging / preprod / prod 之間的差異視為一級風險，按漂移來源分類偵測與治理">6.15 Environment Parity&lt;/a> 評估 staging 差異的實際風險，再到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 Feature Flag Governance&lt;/a> 建立 flag safety 機制。production validation 的證據回寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Honeycomb 團隊是 test in production 理念的主要推動者之一。Production excellence 的核心責任是把 production 觀測能力提升到可以安全驗證變更的水準。當觀測能力足夠細緻，團隊可以在真實流量下驗證行為，降低對 staging 環境的依賴。</p>
<h2 id="問題場景">問題場景</h2>
<p>Staging 跟 production 之間的差異是結構性的 — 資料量不同、流量模式不同、依賴行為不同、cache 狀態不同。團隊投入大量精力維護 staging parity，但差異仍然存在，staging 通過但 production 失敗的事故反覆出現。</p>
<p>Honeycomb 提出的替代思路是：與其追求 staging 完美複製 production，不如提升 production 的觀測能力，讓驗證可以安全地在 production 執行。這個思路的前提是三個能力同時到位：high-cardinality observability 能即時看見異常、feature flag 能控制變更的可見範圍、automated rollback 能在問題擴大前收回變更。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Observability readiness</td>
          <td>觀測能否按 tenant / path / feature 切分</td>
          <td>high-cardinality trace / structured event</td>
      </tr>
      <tr>
          <td>Feature flag safety</td>
          <td>變更可見範圍是否可控</td>
          <td>dark launch + kill switch</td>
      </tr>
      <tr>
          <td>Progressive validation</td>
          <td>每一步放量是否有即時回饋</td>
          <td>canary → observe → expand 循環</td>
      </tr>
      <tr>
          <td>Rollback readiness</td>
          <td>異常出現時能否自動收回</td>
          <td>automated rollback on anomaly trigger</td>
      </tr>
  </tbody>
</table>
<p>Observability readiness 是整個流程的前提。high-cardinality tracing 讓團隊可以按 tenant、feature flag 狀態、request path 等維度切分觀測資料，在問題只影響少量使用者時就被發現。若觀測只有聚合指標（平均 latency、總 error rate），異常會被稀釋到看不見，等到聚合指標也惡化時影響已經擴大。</p>
<p>Feature flag safety 控制變更的 blast radius。dark launch 讓新邏輯在 production 執行但結果不對外可見，用來驗證效能與正確性。kill switch 讓團隊在異常出現時立即關閉新邏輯，不需要等 redeploy。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>trace cardinality coverage</td>
          <td>觀測維度是否足以切分異常</td>
          <td><a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3</a></td>
      </tr>
      <tr>
          <td>flag rollout anomaly</td>
          <td>新 flag 開啟後行為是否偏離</td>
          <td><a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17</a></td>
      </tr>
      <tr>
          <td>production validation pass</td>
          <td>驗證結果是否支持繼續放量</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>rollback trigger count</td>
          <td>自動回退是否被觸發</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把 test in production 當成「跳過 staging 測試」的簡稱會帶來嚴重風險。test in production 的安全性建立在三個前提上：觀測能力能即時看見異常、feature flag 能控制影響範圍、rollback 能在秒級生效。缺少任何一個前提就直接在 production 測試，只是把風險從 staging 搬到 production，而且 production 的失敗成本更高。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先回到 <a href="/blog/backend/06-reliability/environment-parity/" data-link-title="6.15 Environment Parity 與漂移控制" data-link-desc="把 staging / preprod / prod 之間的差異視為一級風險，按漂移來源分類偵測與治理">6.15 Environment Parity</a> 評估 staging 差異的實際風險，再到 <a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 Feature Flag Governance</a> 建立 flag safety 機制。production validation 的證據回寫 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a> 與 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.honeycomb.io/blog/observability-every-engineers-job-not-just-ops-problem">Observability: It&rsquo;s Every Engineer&rsquo;s Job, Not Just Ops&rsquo; Problem</a></li>
<li><a href="https://www.honeycomb.io/resources/getting-started/what-is-observability-engineering">What Is Observability Engineering?</a></li>
</ul>
]]></content:encoded></item><item><title>LinkedIn：Automated Load Testing 與 Capacity Forecasting</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/</guid><description>&lt;p>Automated load testing 的核心責任是把壓測從一次性活動變成持續回饋的工程流程。Capacity forecasting 的責任是用歷史流量趨勢加上壓測結果，預測什麼時候需要擴容、什麼時候可以縮減。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>大型社交平台的流量增長是漸進的，但容量不足是突然的。超過 saturation point 後 latency 會非線性惡化，從可接受的排隊延遲快速轉成級聯超時。若靠一次性壓測做容量規劃，規劃結論會隨時間漂移：流量結構改變、功能上線帶進新 workload、依賴服務的回應時間波動，都會讓上一次壓測的 saturation point 不再準確。&lt;/p>
&lt;p>LinkedIn 的做法是把壓測自動化並跑在定期排程中，讓容量預測的輸入持續更新。壓測結果直接餵給 forecasting 模型，forecasting 輸出接到 headroom alert，headroom alert 觸發擴容 review。這條鏈路讓容量決策從「每季做一次人工判斷」變成「每週自動更新、異常時才需要人介入」。&lt;/p>
&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>Automated load test&lt;/td>
 &lt;td>saturation point 是否仍準確&lt;/td>
 &lt;td>更新後的容量基準&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Traffic forecasting&lt;/td>
 &lt;td>未來 N 天的 peak load 是否會逼近上限&lt;/td>
 &lt;td>擴容時間窗預測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Headroom alert&lt;/td>
 &lt;td>forecast / ceiling 比值是否超過門檻&lt;/td>
 &lt;td>自動擴容 review&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Capacity budget&lt;/td>
 &lt;td>每個服務的容量開銷是否在預算內&lt;/td>
 &lt;td>超支 justification&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Automated load test 用 production traffic replay 而非固定 scenario，讓壓測的 workload model 跟真實流量保持同步。Traffic forecasting 結合歷史流量趨勢與產品 launch 日曆，把可預期的流量事件（功能上線、促銷、季節性增長）納入預測。Headroom alert 在 forecast peak / capacity ceiling 比值超過 70-80% 時觸發，讓團隊在容量耗盡前有足夠反應窗口。&lt;/p>
&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>saturation point drift&lt;/td>
 &lt;td>壓測結果是否隨時間漂移&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>headroom ratio&lt;/td>
 &lt;td>peak load 與 capacity ceiling 比值&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>forecast accuracy&lt;/td>
 &lt;td>預測與實際 peak 的偏差&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>capacity spend trend&lt;/td>
 &lt;td>容量成本是否超出預算&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>自動化壓測最常見的失真來源是 workload model 僵化。若自動化跑的是建立時的固定 scenario 而非持續更新的 traffic replay，時間一長模型就跟 production 脫鉤。脫鉤的訊號是壓測結果與 production 同時段的 latency distribution 開始偏離 — p50 / p95 / p99 的比率差異超過 20% 時，模型已需要校準。&lt;/p>
&lt;p>另一個陷阱是把 forecast 當成精確預測。Forecasting 的價值在於提早觸發 review，讓團隊有時間做擴容決策。若團隊把 forecast 當成精確數字做自動擴容，預測偏差會直接變成過度擴容或擴容不足。forecast 輸出應該驅動人工 review，而非直接觸發資源變更。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先把壓測結果接到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load testing&lt;/a> 的 workload model 校準流程，再用 headroom ratio 餵給 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量與成本邊界&lt;/a> 做容量預算。forecast 準確度的追蹤連到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate&lt;/a> 的 baseline 校準。&lt;/p></description><content:encoded><![CDATA[<p>Automated load testing 的核心責任是把壓測從一次性活動變成持續回饋的工程流程。Capacity forecasting 的責任是用歷史流量趨勢加上壓測結果，預測什麼時候需要擴容、什麼時候可以縮減。</p>
<h2 id="問題場景">問題場景</h2>
<p>大型社交平台的流量增長是漸進的，但容量不足是突然的。超過 saturation point 後 latency 會非線性惡化，從可接受的排隊延遲快速轉成級聯超時。若靠一次性壓測做容量規劃，規劃結論會隨時間漂移：流量結構改變、功能上線帶進新 workload、依賴服務的回應時間波動，都會讓上一次壓測的 saturation point 不再準確。</p>
<p>LinkedIn 的做法是把壓測自動化並跑在定期排程中，讓容量預測的輸入持續更新。壓測結果直接餵給 forecasting 模型，forecasting 輸出接到 headroom alert，headroom alert 觸發擴容 review。這條鏈路讓容量決策從「每季做一次人工判斷」變成「每週自動更新、異常時才需要人介入」。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Automated load test</td>
          <td>saturation point 是否仍準確</td>
          <td>更新後的容量基準</td>
      </tr>
      <tr>
          <td>Traffic forecasting</td>
          <td>未來 N 天的 peak load 是否會逼近上限</td>
          <td>擴容時間窗預測</td>
      </tr>
      <tr>
          <td>Headroom alert</td>
          <td>forecast / ceiling 比值是否超過門檻</td>
          <td>自動擴容 review</td>
      </tr>
      <tr>
          <td>Capacity budget</td>
          <td>每個服務的容量開銷是否在預算內</td>
          <td>超支 justification</td>
      </tr>
  </tbody>
</table>
<p>Automated load test 用 production traffic replay 而非固定 scenario，讓壓測的 workload model 跟真實流量保持同步。Traffic forecasting 結合歷史流量趨勢與產品 launch 日曆，把可預期的流量事件（功能上線、促銷、季節性增長）納入預測。Headroom alert 在 forecast peak / capacity ceiling 比值超過 70-80% 時觸發，讓團隊在容量耗盡前有足夠反應窗口。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>saturation point drift</td>
          <td>壓測結果是否隨時間漂移</td>
          <td><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2</a></td>
      </tr>
      <tr>
          <td>headroom ratio</td>
          <td>peak load 與 capacity ceiling 比值</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td>forecast accuracy</td>
          <td>預測與實際 peak 的偏差</td>
          <td><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13</a></td>
      </tr>
      <tr>
          <td>capacity spend trend</td>
          <td>容量成本是否超出預算</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>自動化壓測最常見的失真來源是 workload model 僵化。若自動化跑的是建立時的固定 scenario 而非持續更新的 traffic replay，時間一長模型就跟 production 脫鉤。脫鉤的訊號是壓測結果與 production 同時段的 latency distribution 開始偏離 — p50 / p95 / p99 的比率差異超過 20% 時，模型已需要校準。</p>
<p>另一個陷阱是把 forecast 當成精確預測。Forecasting 的價值在於提早觸發 review，讓團隊有時間做擴容決策。若團隊把 forecast 當成精確數字做自動擴容，預測偏差會直接變成過度擴容或擴容不足。forecast 輸出應該驅動人工 review，而非直接觸發資源變更。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先把壓測結果接到 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load testing</a> 的 workload model 校準流程，再用 headroom ratio 餵給 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量與成本邊界</a> 做容量預算。forecast 準確度的追蹤連到 <a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a> 的 baseline 校準。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2019/eliminating-toil-with-fully-automated-load-testing">Eliminating toil with fully automated load testing</a></li>
<li>（背景脈絡）<a href="https://engineering.linkedin.com/performance/taming-database-replication-latency-capacity-planning">Taming Database Replication Latency by Capacity Planning</a></li>
</ul>
]]></content:encoded></item><item><title>3.C32 LoyaltyLion：監控數千 RabbitMQ queue</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/</guid><description>&lt;p>這個案例的核心責任是說明大規模 queue topology 的監控議題超出 Management plugin 能力範圍。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LoyaltyLion 跑數千個 RabbitMQ queue、用 rabbitmqctl 跑 recurring script 抓 queue 資訊、透過 statsd 送到 Datadog。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>大規模 queue 拓撲下管理 plugin API 不夠用、需自寫採集腳本。揭露 queue 數量上萬時、原生 monitoring 介面（HTTP API、Management UI）會變成瓶頸、需要 metrics agent 模式。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發（大規模 queue topology 的監控議題）/ RabbitMQ Cluster Operator（運維邊界）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 觀測模組&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.loyaltylion.com/monitoring-thousands-of-rabbitmq-queues-with-datadog-d3168c088ea6">Monitoring Thousands of RabbitMQ Queues with Datadog&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模 queue topology 的監控議題超出 Management plugin 能力範圍。</p>
<h2 id="觀察">觀察</h2>
<p>LoyaltyLion 跑數千個 RabbitMQ queue、用 rabbitmqctl 跑 recurring script 抓 queue 資訊、透過 statsd 送到 Datadog。</p>
<h2 id="判讀">判讀</h2>
<p>大規模 queue 拓撲下管理 plugin API 不夠用、需自寫採集腳本。揭露 queue 數量上萬時、原生 monitoring 介面（HTTP API、Management UI）會變成瓶頸、需要 metrics agent 模式。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發（大規模 queue topology 的監控議題）/ RabbitMQ Cluster Operator（運維邊界）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 觀測模組</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.loyaltylion.com/monitoring-thousands-of-rabbitmq-queues-with-datadog-d3168c088ea6">Monitoring Thousands of RabbitMQ Queues with Datadog</a></li>
</ul>
]]></content:encoded></item><item><title>9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/</guid><description>&lt;p>這個案例的核心責任是補強 Azure DB-OLTP 維度缺口。Clearent 是美國的中型支付處理商、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered 跨市場銀行 OLTP&lt;/a> 形成對照 — 一個是合規驅動的跨市場分割、一個是單一規模的高吞吐處理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Clearent 在 Azure SQL Hyperscale 的關鍵敘述（引自 &lt;a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent Customer Story&lt;/a>）：&lt;/p>
&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>5 億筆&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶基礎&lt;/td>
 &lt;td>各種規模 merchants（中小型為主）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Azure SQL Database Hyperscale 服務級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構模式&lt;/td>
 &lt;td>modern microservices architecture&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴展能力&lt;/td>
 &lt;td>「scale automatically and almost infinitely」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發特性&lt;/td>
 &lt;td>「tens of thousands of users 同時存取」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務驅動&lt;/td>
 &lt;td>「unite all its information in one place」+ 「faster insights」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵特性：Azure SQL Hyperscale 把 storage 跟 compute 分離、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a> 的 Aurora 是同類設計。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Clearent 案例揭露三個 Hyperscale 設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>5 億筆 / 年 ≈ 1500 筆 / 秒平均、但 peak 可能 10-50x&lt;/strong>：支付交易有日內 / 月內 / 季內節律。早上 9-11 點商家對帳高峰、下午 12-1 點消費高峰、晚上 6-8 點消費高峰、月底結算高峰。容量規劃必須按 peak 訂、不是平均。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling&lt;/a> 的 peak/avg ratio 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>Hyperscale = storage / compute 解耦&lt;/strong>：傳統 SQL Server primary 對 storage 跟 CPU / RAM 綁定、擴 storage 就要換更大 instance、不便。Hyperscale 把 storage 拉到分散式 log service、可以獨立擴 storage（最高 100 TB）、compute 獨立擴。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a> 的同類分離思維、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a>。&lt;/li>
&lt;li>&lt;strong>「unite all information in one place」是支付業的特殊需求&lt;/strong>：merchants 需要對帳、退款、清算、稅務報表都即時可查、不能 OLAP 分開。Hyperscale 的 read scale-out（最多 4 個 secondary replica）讓即時報表跑在 OLTP DB 上不影響交易吞吐。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「scale automatically and almost infinitely」是行銷敘述。實際 Hyperscale 有上限（100 TB storage、Gen5 series 80 vCore）、超過要 sharding 應用層分散。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure DB-OLTP 維度缺口。Clearent 是美國的中型支付處理商、跟 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered 跨市場銀行 OLTP</a> 形成對照 — 一個是合規驅動的跨市場分割、一個是單一規模的高吞吐處理。</p>
<h2 id="觀察">觀察</h2>
<p>Clearent 在 Azure SQL Hyperscale 的關鍵敘述（引自 <a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年交易量</td>
          <td>5 億筆</td>
      </tr>
      <tr>
          <td>客戶基礎</td>
          <td>各種規模 merchants（中小型為主）</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Azure SQL Database Hyperscale 服務級</td>
      </tr>
      <tr>
          <td>架構模式</td>
          <td>modern microservices architecture</td>
      </tr>
      <tr>
          <td>擴展能力</td>
          <td>「scale automatically and almost infinitely」</td>
      </tr>
      <tr>
          <td>並發特性</td>
          <td>「tens of thousands of users 同時存取」</td>
      </tr>
      <tr>
          <td>業務驅動</td>
          <td>「unite all its information in one place」+ 「faster insights」</td>
      </tr>
  </tbody>
</table>
<p>關鍵特性：Azure SQL Hyperscale 把 storage 跟 compute 分離、跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的 Aurora 是同類設計。</p>
<h2 id="判讀">判讀</h2>
<p>Clearent 案例揭露三個 Hyperscale 設計的工程重點。</p>
<ol>
<li><strong>5 億筆 / 年 ≈ 1500 筆 / 秒平均、但 peak 可能 10-50x</strong>：支付交易有日內 / 月內 / 季內節律。早上 9-11 點商家對帳高峰、下午 12-1 點消費高峰、晚上 6-8 點消費高峰、月底結算高峰。容量規劃必須按 peak 訂、不是平均。對應 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 的 peak/avg ratio 跟 <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>。</li>
<li><strong>Hyperscale = storage / compute 解耦</strong>：傳統 SQL Server primary 對 storage 跟 CPU / RAM 綁定、擴 storage 就要換更大 instance、不便。Hyperscale 把 storage 拉到分散式 log service、可以獨立擴 storage（最高 100 TB）、compute 獨立擴。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的同類分離思維、跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a>。</li>
<li><strong>「unite all information in one place」是支付業的特殊需求</strong>：merchants 需要對帳、退款、清算、稅務報表都即時可查、不能 OLAP 分開。Hyperscale 的 read scale-out（最多 4 個 secondary replica）讓即時報表跑在 OLTP DB 上不影響交易吞吐。</li>
</ol>
<p>需要警惕：「scale automatically and almost infinitely」是行銷敘述。實際 Hyperscale 有上限（100 TB storage、Gen5 series 80 vCore）、超過要 sharding 應用層分散。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Hyperscale 跟 Aurora 是同類設計、選型按生態</strong>：Azure 生態用 Hyperscale、AWS 生態用 Aurora、GCP 用 AlloyDB / Spanner。三家底層工程哲學一致（log-structured storage、storage / compute 分離）、選哪家取決於 application 已在哪個 cloud。</li>
<li><strong>微服務 + 共用 OLTP 是支付業常見架構</strong>：服務拆細、但 OLTP 仍是 single source of truth、共用一個 Hyperscale cluster。這跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix microservice 各自 Aurora</a> 不同 — Netflix 每微服務 <em>自己</em> Aurora、Clearent 微服務共用 Hyperscale。取捨：Clearent 的「對帳一致性」需求讓共用更划算。</li>
<li><strong>支付業容量規劃以 peak 為主</strong>：不能用平均 RPS 規劃、要按單日 / 單秒 peak。歷史 peak × 預期成長 × headroom 是基本公式（<a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>）。</li>
</ol>
<p>跨平台等效：AWS Aurora Serverless v2、GCP AlloyDB、Spanner、PostgreSQL 自管 + Patroni 都可實作對等架構。差異是 vendor managed 程度跟 OLAP / OLTP 統一視覺。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 OLTP 案例 → <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> / <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a></li>
<li>想設計支付業容量 → <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a></li>
<li>想理解 storage / compute 分離 → <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent scales its modern microservices architecture to handle 500 million payment transactions a year</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/announcing-azure-sql-database-hyperscale-public-preview/">Announcing Azure SQL Database Hyperscale</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/get-high-performance-scaling-for-your-azure-database-workloads-with-hyperscale/">Get high-performance scaling for your Azure database workloads with Hyperscale</a></li>
</ul>
]]></content:encoded></item><item><title>3.C33 Wargaming：World of Tanks 戰後 dossier 解耦</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/</guid><description>&lt;p>這個案例的核心責任是說明 game server / web portal 異步解耦、queue 吸收戰後事件 burst。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>World of Tanks server 全 Linux、用 RabbitMQ 作為 web service stack 核心。每場戰鬥結束後玩家 tank dossier 寫入 message queue、讓 game portal 顯示最新統計而不增加 game server load。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Queue 是 game server 與 portal 的解耦邊界、subscription 也走 RabbitMQ。揭露遊戲場景的「戰後事件 burst」適合用 queue 吸收、不該打到 game server 內部狀態。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Federation + Shovel（多 region game server 同步）/ 多 vhost + 多租戶（多遊戲共用 broker）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.linuxfoundation.org/blog/blog/wargaming-mobilizes-with-linux-and-open-source">Wargaming Mobilizes with Linux and Open Source (Linux Foundation)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://ftr.wot-news.com/2014/07/17/wargaming-public-api-part-2/">Wargaming Public API&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 game server / web portal 異步解耦、queue 吸收戰後事件 burst。</p>
<h2 id="觀察">觀察</h2>
<p>World of Tanks server 全 Linux、用 RabbitMQ 作為 web service stack 核心。每場戰鬥結束後玩家 tank dossier 寫入 message queue、讓 game portal 顯示最新統計而不增加 game server load。</p>
<h2 id="判讀">判讀</h2>
<p>Queue 是 game server 與 portal 的解耦邊界、subscription 也走 RabbitMQ。揭露遊戲場景的「戰後事件 burst」適合用 queue 吸收、不該打到 game server 內部狀態。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Federation + Shovel（多 region game server 同步）/ 多 vhost + 多租戶（多遊戲共用 broker）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.linuxfoundation.org/blog/blog/wargaming-mobilizes-with-linux-and-open-source">Wargaming Mobilizes with Linux and Open Source (Linux Foundation)</a></li>
<li><a href="http://ftr.wot-news.com/2014/07/17/wargaming-public-api-part-2/">Wargaming Public API</a></li>
</ul>
]]></content:encoded></item><item><title>9.C33 Maersk + Bosch：傳統產業在 Azure AKS 上的微服務治理</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/</guid><description>&lt;p>這個案例的核心責任是補強 Azure compute / K8s 維度缺口。Maersk（全球最大貨櫃航運公司、每天處理百萬級貨櫃移動）跟 Bosch（德國工業集團、智慧建築 IoT）是 &lt;em>傳統產業上雲&lt;/em> 的代表 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 雲原生 EKS&lt;/a> 形成對比、傳統產業的 K8s 採用動機跟雲原生公司不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Maersk + Bosch 在 Azure AKS 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">AKS Customer Stories&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Maersk&lt;/th>
 &lt;th>Bosch Software Innovations&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>行業&lt;/td>
 &lt;td>全球海運&lt;/td>
 &lt;td>工業 IoT（Connected Building Solution）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要 workload&lt;/td>
 &lt;td>貨櫃追蹤、港口物流、行程規劃&lt;/td>
 &lt;td>樓宇感測、能源管理、設備運維&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AKS 用途&lt;/td>
 &lt;td>deployment + 運維 + 管理 Kubernetes API&lt;/td>
 &lt;td>microservices 監控、不同 release cycle&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程訴求&lt;/td>
 &lt;td>「focus on things that makes the most business impact」&lt;/td>
 &lt;td>「simplify management of microservices released on different cycles」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AKS + Azure 管理工具&lt;/td>
 &lt;td>AKS + monitoring capabilities&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>其他常見 AKS 大客戶：Siemens Healthineers（醫療設備）、Finastra（金融軟體）、Hafslund（能源）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Maersk 跟 Bosch 案例揭露三個傳統產業 K8s 治理的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>傳統產業上 K8s 的動機是「治理一致性」、不是「成長彈性」&lt;/strong>：
&lt;ul>
&lt;li>雲原生公司（Riot、Netflix）上 K8s 是為了 &lt;em>快速擴容&lt;/em> 跟 &lt;em>跨 region 部署&lt;/em>&lt;/li>
&lt;li>傳統產業上 K8s 是為了 &lt;em>統一 50+ 個應用團隊的部署流程&lt;/em>、降低 ops 複雜度&lt;/li>
&lt;li>訴求不同、配置不同 — 傳統產業可能用 &lt;em>較大 node、較少 cluster&lt;/em>、不是 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot 246 cluster&lt;/a> 那種多 cluster 策略&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>微服務 release cycle 多元化是傳統產業上 K8s 的核心需求&lt;/strong>：Bosch Connected Building 有「樓宇感測 daily release、能源計費 weekly release、設備運維 monthly release」、每個 release cycle 不同。K8s + GitOps（Argo CD、Flux）讓不同 cycle 共存於同一 cluster。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 release governance。&lt;/li>
&lt;li>&lt;strong>「focus on business impact」是 managed K8s 的真正價值&lt;/strong>：Maersk 不是科技公司、是航運公司。工程資源從 &lt;em>維持 K8s 運維&lt;/em> 釋放到 &lt;em>貨櫃追蹤演算法、港口物流優化&lt;/em>、是商業 ROI 的關鍵。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &amp;#43; AWS Media Services 撐 30 channels live &amp;#43; 5M MAU、工程工時下降 90%">9.C29 Lemino 90% 工程工時下降&lt;/a> 的同類訴求、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Azure 官方對 Maersk / Bosch 的描述偏行銷、缺具體 throughput / latency 數字。讀此類案例要對 &lt;em>策略&lt;/em> 學習、不要套用數字。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure compute / K8s 維度缺口。Maersk（全球最大貨櫃航運公司、每天處理百萬級貨櫃移動）跟 Bosch（德國工業集團、智慧建築 IoT）是 <em>傳統產業上雲</em> 的代表 — 跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 雲原生 EKS</a> 形成對比、傳統產業的 K8s 採用動機跟雲原生公司不同。</p>
<h2 id="觀察">觀察</h2>
<p>Maersk + Bosch 在 Azure AKS 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">AKS Customer Stories</a>）：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Maersk</th>
          <th>Bosch Software Innovations</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行業</td>
          <td>全球海運</td>
          <td>工業 IoT（Connected Building Solution）</td>
      </tr>
      <tr>
          <td>主要 workload</td>
          <td>貨櫃追蹤、港口物流、行程規劃</td>
          <td>樓宇感測、能源管理、設備運維</td>
      </tr>
      <tr>
          <td>AKS 用途</td>
          <td>deployment + 運維 + 管理 Kubernetes API</td>
          <td>microservices 監控、不同 release cycle</td>
      </tr>
      <tr>
          <td>工程訴求</td>
          <td>「focus on things that makes the most business impact」</td>
          <td>「simplify management of microservices released on different cycles」</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AKS + Azure 管理工具</td>
          <td>AKS + monitoring capabilities</td>
      </tr>
  </tbody>
</table>
<p>其他常見 AKS 大客戶：Siemens Healthineers（醫療設備）、Finastra（金融軟體）、Hafslund（能源）。</p>
<h2 id="判讀">判讀</h2>
<p>Maersk 跟 Bosch 案例揭露三個傳統產業 K8s 治理的工程重點。</p>
<ol>
<li><strong>傳統產業上 K8s 的動機是「治理一致性」、不是「成長彈性」</strong>：
<ul>
<li>雲原生公司（Riot、Netflix）上 K8s 是為了 <em>快速擴容</em> 跟 <em>跨 region 部署</em></li>
<li>傳統產業上 K8s 是為了 <em>統一 50+ 個應用團隊的部署流程</em>、降低 ops 複雜度</li>
<li>訴求不同、配置不同 — 傳統產業可能用 <em>較大 node、較少 cluster</em>、不是 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot 246 cluster</a> 那種多 cluster 策略</li>
</ul>
</li>
<li><strong>微服務 release cycle 多元化是傳統產業上 K8s 的核心需求</strong>：Bosch Connected Building 有「樓宇感測 daily release、能源計費 weekly release、設備運維 monthly release」、每個 release cycle 不同。K8s + GitOps（Argo CD、Flux）讓不同 cycle 共存於同一 cluster。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 release governance。</li>
<li><strong>「focus on business impact」是 managed K8s 的真正價值</strong>：Maersk 不是科技公司、是航運公司。工程資源從 <em>維持 K8s 運維</em> 釋放到 <em>貨櫃追蹤演算法、港口物流優化</em>、是商業 ROI 的關鍵。對應 <a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">9.C29 Lemino 90% 工程工時下降</a> 的同類訴求、跟 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>需要警惕：Azure 官方對 Maersk / Bosch 的描述偏行銷、缺具體 throughput / latency 數字。讀此類案例要對 <em>策略</em> 學習、不要套用數字。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>傳統產業 K8s 採用先做「單一 cluster 多 namespace」、再考慮多 cluster</strong>：管理 1 個大 cluster 比管理 246 個小 cluster 容易。除非有 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 的隔離需求</a>、否則 single-cluster-multi-namespace 是 sane default。</li>
<li><strong>不同 release cycle 用 GitOps + namespace 隔離</strong>：每個團隊 own 自己的 namespace、配合 Argo CD / Flux 各自 release。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
<li><strong>AKS / EKS / GKE 的差異對傳統產業不關鍵</strong>：選哪家通常取決於企業已用哪家 cloud、不是 K8s feature 本身。重點是 <em>managed K8s ops 比自管划算</em>、不是哪家 managed 最好。</li>
<li><strong>監控訊號設計按業務 cycle</strong>：每天 release 的服務跟每月 release 的服務 monitoring 策略不同、alert 敏感度不同。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>。</li>
</ol>
<p>跨平台等效：AWS EKS、GCP GKE、自管 Kubernetes + Rancher 都可實作對等架構。Azure 在 enterprise 整合（Active Directory、Azure DevOps）有優勢、特別適合 Microsoft 生態企業。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照雲原生 K8s 策略 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a></li>
<li>對照其他 managed 服務釋放工程資源 → <a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">9.C29 Lemino</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a></li>
<li>想設計 K8s 治理 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">Azure Kubernetes Service customer stories</a></li>
<li><a href="https://customers.microsoft.com/en-us/story/maersk-travel-transportation-azure">Maersk Azure case</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/product/azure-kubernetes-service-aks/">Bosch Software Innovations</a></li>
<li><a href="https://azure.microsoft.com/en-us/solutions/kubernetes-on-azure">Kubernetes on Azure - Enterprise Expertise</a></li>
</ul>
]]></content:encoded></item><item><title>3.C34 Netlify：NATS 當全球 metrics/logs 統一資料平面</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/</guid><description>&lt;p>Netlify 的 NATS 選型示範了 subject-based fan-out 在跨雲觀測資料平面的優勢 — 協議極簡帶來的是部署簡單跟 client 整合成本低，代價是放棄持久化保證。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Netlify 是靜態網站跟 serverless function 的部署平台，服務 70,000+ 網站、近月 10 億 page view。基礎設施橫跨 Rackspace、AWS、GCP、Digital Ocean 四個雲端供應商。每個服務節點都會產生 metrics 跟 logs，需要一條統一的資料路徑把這些訊號從各地收集到中央觀測系統。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="跨雲統一資料平面">跨雲統一資料平面&lt;/h3>
&lt;p>四個雲的服務各自有不同的網路拓樸跟存取方式。觀測資料需要跨雲收集到同一個目的地（Elasticsearch），但直接讓每個服務 HTTP POST 到 Elasticsearch 會有連線管理、背壓、格式轉換的問題分散在每個服務裡。&lt;/p>
&lt;p>Netlify 需要一個中介層 — 各服務把 metrics / logs 推到中介層，中介層負責 fan-out 到下游消費者（Elasticsearch、即時 dashboard、告警系統）。&lt;/p>
&lt;h3 id="選型nats-vs-rabbitmq">選型：NATS vs RabbitMQ&lt;/h3>
&lt;p>Netlify 評估了 RabbitMQ 跟 NATS。RabbitMQ 在功能上更完整（持久化 queue、DLQ、ack 機制），但 Netlify 的觀測資料場景有三個特性讓 NATS 更合適：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>資料可丟&lt;/strong>：metrics 跟 logs 是 best-effort 的觀測資料，遺失幾秒的資料不影響業務 — 持久化保證帶來的運維成本大於收益&lt;/li>
&lt;li>&lt;strong>Fan-out 是主要模式&lt;/strong>：同一份資料要被多個消費者訂閱（Elasticsearch、即時 tail、告警），NATS 的 subject-based pub/sub 天然支援，RabbitMQ 需要設 exchange + 多個 binding&lt;/li>
&lt;li>&lt;strong>部署極簡&lt;/strong>：NATS server 是單一 binary、零依賴、幾秒鐘啟動，跨四個雲部署時每個雲跑一個 NATS node 的運維成本遠低於 RabbitMQ cluster&lt;/li>
&lt;/ul>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="架構">架構&lt;/h3>
&lt;p>Netlify 用 Core NATS（非 JetStream）搭建觀測資料平面：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Producer 端&lt;/strong>：用 logrus 的 NATS hook 讓所有 Go 服務的 structured log 自動推到 NATS subject；另用 log-tail 工具從 file-based log 讀取推送&lt;/li>
&lt;li>&lt;strong>Consumer 端&lt;/strong>：一個 elastinats 消費者訂閱 NATS subject、批次寫入 Elasticsearch；其他消費者可以各自訂閱同一個 subject 做即時處理&lt;/li>
&lt;/ul>
&lt;p>Subject 的命名用階層式結構（例如 &lt;code>logs.production.api&lt;/code>），讓消費者可以用 wildcard 訂閱整個子樹（&lt;code>logs.production.*&lt;/code>）或特定服務。&lt;/p>
&lt;h3 id="取捨">取捨&lt;/h3>
&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>放棄（Core NATS）&lt;/td>
 &lt;td>NATS server 重啟時 in-flight 的訊息遺失&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ack 機制&lt;/td>
 &lt;td>放棄（fire-and-forget）&lt;/td>
 &lt;td>Consumer 處理失敗的訊息不會被重送&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨雲連接&lt;/td>
 &lt;td>NATS cluster&lt;/td>
 &lt;td>需要跨雲的網路連線、延遲影響 cluster 一致性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consumer 擴展&lt;/td>
 &lt;td>多個訂閱者各自訂閱&lt;/td>
 &lt;td>每個消費者收到全量資料、沒有 consumer group 的分攤機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Core NATS 的 fire-and-forget 語意在觀測資料場景是有意的選擇 — 觀測資料的價值隨時間快速衰減，遺失一秒鐘的 metrics 不影響趨勢判讀。如果場景需要持久化（例：audit log、交易事件），Core NATS 就不適合，需要 JetStream 或其他有持久化保證的 broker。&lt;/p>
&lt;h2 id="回寫教材的連結">回寫教材的連結&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>：Core NATS 的 fire-and-forget 是 broker 可靠性光譜的一端（at-most-once），Kafka 跟 RabbitMQ 在另一端（at-least-once / durable）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a>：Core NATS vs JetStream 的選型判準 — 本案例是純 Core NATS 的代表場景&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a>：Netlify 的 NATS 資料平面在觀測 pipeline 架構中扮演 collector 跟 storage 之間的 transport 層&lt;/li>
&lt;/ul>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;p>讀者在自己的系統看到以下訊號時，應該回讀本案例：&lt;/p></description><content:encoded><![CDATA[<p>Netlify 的 NATS 選型示範了 subject-based fan-out 在跨雲觀測資料平面的優勢 — 協議極簡帶來的是部署簡單跟 client 整合成本低，代價是放棄持久化保證。</p>
<h2 id="業務背景">業務背景</h2>
<p>Netlify 是靜態網站跟 serverless function 的部署平台，服務 70,000+ 網站、近月 10 億 page view。基礎設施橫跨 Rackspace、AWS、GCP、Digital Ocean 四個雲端供應商。每個服務節點都會產生 metrics 跟 logs，需要一條統一的資料路徑把這些訊號從各地收集到中央觀測系統。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="跨雲統一資料平面">跨雲統一資料平面</h3>
<p>四個雲的服務各自有不同的網路拓樸跟存取方式。觀測資料需要跨雲收集到同一個目的地（Elasticsearch），但直接讓每個服務 HTTP POST 到 Elasticsearch 會有連線管理、背壓、格式轉換的問題分散在每個服務裡。</p>
<p>Netlify 需要一個中介層 — 各服務把 metrics / logs 推到中介層，中介層負責 fan-out 到下游消費者（Elasticsearch、即時 dashboard、告警系統）。</p>
<h3 id="選型nats-vs-rabbitmq">選型：NATS vs RabbitMQ</h3>
<p>Netlify 評估了 RabbitMQ 跟 NATS。RabbitMQ 在功能上更完整（持久化 queue、DLQ、ack 機制），但 Netlify 的觀測資料場景有三個特性讓 NATS 更合適：</p>
<ul>
<li><strong>資料可丟</strong>：metrics 跟 logs 是 best-effort 的觀測資料，遺失幾秒的資料不影響業務 — 持久化保證帶來的運維成本大於收益</li>
<li><strong>Fan-out 是主要模式</strong>：同一份資料要被多個消費者訂閱（Elasticsearch、即時 tail、告警），NATS 的 subject-based pub/sub 天然支援，RabbitMQ 需要設 exchange + 多個 binding</li>
<li><strong>部署極簡</strong>：NATS server 是單一 binary、零依賴、幾秒鐘啟動，跨四個雲部署時每個雲跑一個 NATS node 的運維成本遠低於 RabbitMQ cluster</li>
</ul>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="架構">架構</h3>
<p>Netlify 用 Core NATS（非 JetStream）搭建觀測資料平面：</p>
<ul>
<li><strong>Producer 端</strong>：用 logrus 的 NATS hook 讓所有 Go 服務的 structured log 自動推到 NATS subject；另用 log-tail 工具從 file-based log 讀取推送</li>
<li><strong>Consumer 端</strong>：一個 elastinats 消費者訂閱 NATS subject、批次寫入 Elasticsearch；其他消費者可以各自訂閱同一個 subject 做即時處理</li>
</ul>
<p>Subject 的命名用階層式結構（例如 <code>logs.production.api</code>），讓消費者可以用 wildcard 訂閱整個子樹（<code>logs.production.*</code>）或特定服務。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>選擇</th>
          <th>代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>持久化</td>
          <td>放棄（Core NATS）</td>
          <td>NATS server 重啟時 in-flight 的訊息遺失</td>
      </tr>
      <tr>
          <td>Ack 機制</td>
          <td>放棄（fire-and-forget）</td>
          <td>Consumer 處理失敗的訊息不會被重送</td>
      </tr>
      <tr>
          <td>跨雲連接</td>
          <td>NATS cluster</td>
          <td>需要跨雲的網路連線、延遲影響 cluster 一致性</td>
      </tr>
      <tr>
          <td>Consumer 擴展</td>
          <td>多個訂閱者各自訂閱</td>
          <td>每個消費者收到全量資料、沒有 consumer group 的分攤機制</td>
      </tr>
  </tbody>
</table>
<p>Core NATS 的 fire-and-forget 語意在觀測資料場景是有意的選擇 — 觀測資料的價值隨時間快速衰減，遺失一秒鐘的 metrics 不影響趨勢判讀。如果場景需要持久化（例：audit log、交易事件），Core NATS 就不適合，需要 JetStream 或其他有持久化保證的 broker。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：Core NATS 的 fire-and-forget 是 broker 可靠性光譜的一端（at-most-once），Kafka 跟 RabbitMQ 在另一端（at-least-once / durable）</li>
<li><a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a>：Core NATS vs JetStream 的選型判準 — 本案例是純 Core NATS 的代表場景</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a>：Netlify 的 NATS 資料平面在觀測 pipeline 架構中扮演 collector 跟 storage 之間的 transport 層</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測資料（metrics / logs）需要跨多個雲或多個 datacenter 收集到中央系統</li>
<li>現有的 broker（RabbitMQ / Kafka）在觀測資料場景的運維成本跟資料價值不成比例</li>
<li>Fan-out 是主要消費模式 — 同一份資料需要被多個下游系統訂閱</li>
<li>對 message delivery 的可靠性要求是 best-effort 而非 at-least-once</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/netlify-nats-blog/">Why Netlify chose NATS</a></li>
</ul>
]]></content:encoded></item><item><title>9.C34 GCP：130,000-node GKE cluster 的工程極限</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/</guid><description>&lt;p>這個案例的核心責任是揭示「現代 AI workload 對 Kubernetes 規模極限的拉扯」。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster&lt;/a> 走「多小 cluster 隔離」相反 — GCP 內部驗證的是「單一巨大 cluster 集中管理」、為前沿 LLM 訓練的萬卡叢集需求設計。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GCP 130K-node GKE cluster 實驗（引自 &lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster&lt;/a>）：&lt;/p>
&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>130,000（vs 官方支援 65,000）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod 創建峰值&lt;/td>
 &lt;td>1,000 Pods / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Phase 1 deploy 時間&lt;/td>
 &lt;td>130,000 Pods in 3 分 40 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Phase 2 batch 創建&lt;/td>
 &lt;td>65,000 Pods in 81 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Preemption 峰值&lt;/td>
 &lt;td>39,000 Pods preempted in 93 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod startup p99&lt;/td>
 &lt;td>~10 秒（inference workload）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API server LIST p99&lt;/td>
 &lt;td>「well below defined thresholds」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database objects&lt;/td>
 &lt;td>100 萬 +&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lease 更新 QPS&lt;/td>
 &lt;td>13,000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶當前範圍&lt;/td>
 &lt;td>20-65K node range&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>預期 cluster size 穩定&lt;/td>
 &lt;td>100K node mark&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>工作負載類型：AI / ML 平台、三個 priority class：&lt;/p>
&lt;ul>
&lt;li>Low：preemptible batch（data prep）&lt;/li>
&lt;li>Medium：core model training（tolerant to queuing）&lt;/li>
&lt;li>High：latency-sensitive inference&lt;/li>
&lt;/ul>
&lt;p>關鍵 control plane 設計：&lt;/p>
&lt;ul>
&lt;li>Consistent Reads from Cache（KEP-2340）— 強一致 read 從 in-memory cache、不打 storage&lt;/li>
&lt;li>Snapshottable API Server Cache（KEP-4988）— B-tree snapshot 處理 LIST 請求&lt;/li>
&lt;li>Spanner-based key-value store 作為 K8s storage backend（撐 13K QPS lease 更新）&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>130K-node 案例揭露三個 hyperscale K8s 設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單一 control plane 的極限取決於 storage backend、不是 nodes&lt;/strong>：130K node 不是「機器跑不動」、是「API server 跟 etcd 撐不撐住」。GCP 用 Spanner 替換 etcd、配上 cache-first read 設計、把 storage 從瓶頸變成「showed no signs of not being able to support higher scales」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程&lt;/a> 的「真實 bottleneck 在哪一層」。&lt;/li>
&lt;li>&lt;strong>AI workload 顛覆了 K8s 容量規劃&lt;/strong>：傳統 web workload 的 K8s 多在 1K-10K node、節點生命週期長。AI workload 短時間爆量創建跟銷毀 Pods（13 萬個 in 3 分 40 秒）、preempt 跟 schedule 頻繁、對 control plane 是完全不同壓力模式。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling&lt;/a> — workload 形狀完全不同、容量規劃也完全不同。&lt;/li>
&lt;li>&lt;strong>「power constraint &amp;gt; chip supply」是新瓶頸&lt;/strong>：單顆 NVIDIA GB200 GPU 吃 2700W、萬卡叢集 = 27MW 用電量。未來 mega cluster 必須跨多個 data center（一個 DC 電力撐不住）、需要 &lt;em>robust multi-cluster solutions&lt;/em>。這層瓶頸跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界&lt;/a> 對接 — 電力成本變成主要 cost driver。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭示「現代 AI workload 對 Kubernetes 規模極限的拉扯」。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a> 走「多小 cluster 隔離」相反 — GCP 內部驗證的是「單一巨大 cluster 集中管理」、為前沿 LLM 訓練的萬卡叢集需求設計。</p>
<h2 id="觀察">觀察</h2>
<p>GCP 130K-node GKE cluster 實驗（引自 <a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實驗節點數</td>
          <td>130,000（vs 官方支援 65,000）</td>
      </tr>
      <tr>
          <td>Pod 創建峰值</td>
          <td>1,000 Pods / 秒</td>
      </tr>
      <tr>
          <td>Phase 1 deploy 時間</td>
          <td>130,000 Pods in 3 分 40 秒</td>
      </tr>
      <tr>
          <td>Phase 2 batch 創建</td>
          <td>65,000 Pods in 81 秒</td>
      </tr>
      <tr>
          <td>Preemption 峰值</td>
          <td>39,000 Pods preempted in 93 秒</td>
      </tr>
      <tr>
          <td>Pod startup p99</td>
          <td>~10 秒（inference workload）</td>
      </tr>
      <tr>
          <td>API server LIST p99</td>
          <td>「well below defined thresholds」</td>
      </tr>
      <tr>
          <td>Database objects</td>
          <td>100 萬 +</td>
      </tr>
      <tr>
          <td>Lease 更新 QPS</td>
          <td>13,000</td>
      </tr>
      <tr>
          <td>客戶當前範圍</td>
          <td>20-65K node range</td>
      </tr>
      <tr>
          <td>預期 cluster size 穩定</td>
          <td>100K node mark</td>
      </tr>
  </tbody>
</table>
<p>工作負載類型：AI / ML 平台、三個 priority class：</p>
<ul>
<li>Low：preemptible batch（data prep）</li>
<li>Medium：core model training（tolerant to queuing）</li>
<li>High：latency-sensitive inference</li>
</ul>
<p>關鍵 control plane 設計：</p>
<ul>
<li>Consistent Reads from Cache（KEP-2340）— 強一致 read 從 in-memory cache、不打 storage</li>
<li>Snapshottable API Server Cache（KEP-4988）— B-tree snapshot 處理 LIST 請求</li>
<li>Spanner-based key-value store 作為 K8s storage backend（撐 13K QPS lease 更新）</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>130K-node 案例揭露三個 hyperscale K8s 設計的工程重點。</p>
<ol>
<li><strong>單一 control plane 的極限取決於 storage backend、不是 nodes</strong>：130K node 不是「機器跑不動」、是「API server 跟 etcd 撐不撐住」。GCP 用 Spanner 替換 etcd、配上 cache-first read 設計、把 storage 從瓶頸變成「showed no signs of not being able to support higher scales」。對應 <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a> 的「真實 bottleneck 在哪一層」。</li>
<li><strong>AI workload 顛覆了 K8s 容量規劃</strong>：傳統 web workload 的 K8s 多在 1K-10K node、節點生命週期長。AI workload 短時間爆量創建跟銷毀 Pods（13 萬個 in 3 分 40 秒）、preempt 跟 schedule 頻繁、對 control plane 是完全不同壓力模式。對應 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> — workload 形狀完全不同、容量規劃也完全不同。</li>
<li><strong>「power constraint &gt; chip supply」是新瓶頸</strong>：單顆 NVIDIA GB200 GPU 吃 2700W、萬卡叢集 = 27MW 用電量。未來 mega cluster 必須跨多個 data center（一個 DC 電力撐不住）、需要 <em>robust multi-cluster solutions</em>。這層瓶頸跟 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界</a> 對接 — 電力成本變成主要 cost driver。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>130K-node 是 <em>Google 內部實驗</em>、不是 <em>客戶能用的 production</em> 配置。目前 GKE 官方支援 65K node、客戶用到 100K+ 還很遠。</li>
<li>AI workload 跟 web workload 完全不同、把 AI 經驗套用到 web service 容量規劃是錯誤類比。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>K8s control plane 跟 data plane 分開規劃容量</strong>：data plane（worker nodes）擴容容易、control plane（API server、etcd / storage）擴容難。瓶頸通常在 control plane、不是 worker。</li>
<li><strong>storage backend 是 K8s 規模極限的關鍵</strong>：etcd 撐 5K-10K node 後開始吃力、要用 PostgreSQL / Spanner / 自家 KV 替換、才能擴到萬級節點。一般客戶用不到、但要知道「為什麼到某個規模 etcd 不夠」。</li>
<li><strong>AI workload 用 specialized scheduler</strong>（Kueue、Volcano）：默認 K8s scheduler 為 web workload 設計、AI 的 gang scheduling、fair-sharing、preemption 都不太適合。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 scheduler 選型。</li>
<li><strong>power-aware capacity planning 是未來方向</strong>：傳統按 CPU / RAM 規劃容量、未來要加上 <em>power budget</em>。data center 用電量是硬上限、不是錢的問題。</li>
<li><strong>multi-cluster 是萬卡訓練的必然</strong>：單一 cluster 撐不住、要 MultiKueue 等跨 cluster 排程方案。對應 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games multi-cluster</a> 但目的完全不同。</li>
</ol>
<p>跨平台等效：AWS EKS 官方支援單 cluster 多至 100K pod / cluster、Azure AKS 支援 5K node / cluster。GCP 用 Spanner 替換 etcd 是最深的工程投資、目前其他兩家還沒到這個規模。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模 K8s → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a>（多 cluster 策略）</li>
<li>對照 AI workload → <a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokemon GO 50x surge</a>（非 AI 但同 GCP K8s）</li>
<li>想理解 control plane vs data plane → <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a> + <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a></li>
<li>想設計 K8s 容量上限 → <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a> + <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster</a></li>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/gke-and-kubernetes-at-kubecon-2025">GKE and Kubernetes at KubeCon 2025</a></li>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/whats-new-in-gke-at-next26">What&rsquo;s new in GKE at Next 26</a></li>
</ul>
]]></content:encoded></item><item><title>3.C35 Form3：NATS JetStream 多雲低延遲支付</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/</guid><description>&lt;p>這個案例的核心責任是說明 JetStream Leaf Node 在跨地理 / 跨雲 durability 拓樸的關鍵角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Form3 服務 Tier-1 銀行（含 Mastercard、Square 等）、要求 500ms 端到端 SLA、AWS SNS/SQS 約 300ms 延遲吃掉預算。在 Faster Payments 機房資源受限下、用 NATS + JetStream 替換 legacy pub/sub bus、達到約 6× 延遲改善並做到「AWS 整個 region 掛掉時不喪失處理能力」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 JetStream 的 Leaf Node 做跨雲橋接、把 on-prem Faster Payments 機房跟雲端 cluster 連起來。揭露金融支付對端到端 latency 預算的硬要求逼出特定 broker 選型、不是「Kafka / SQS 通用化」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream stream 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS&lt;/a>（跨區對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/blog/how-form3-built-a-multi-cloud-low-latency-payments-service-with-nats-io-jetstream">How Form3 Built a Multi-Cloud Low-Latency Payments Service with NATS JetStream (Synadia blog)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 JetStream Leaf Node 在跨地理 / 跨雲 durability 拓樸的關鍵角色。</p>
<h2 id="觀察">觀察</h2>
<p>Form3 服務 Tier-1 銀行（含 Mastercard、Square 等）、要求 500ms 端到端 SLA、AWS SNS/SQS 約 300ms 延遲吃掉預算。在 Faster Payments 機房資源受限下、用 NATS + JetStream 替換 legacy pub/sub bus、達到約 6× 延遲改善並做到「AWS 整個 region 掛掉時不喪失處理能力」。</p>
<h2 id="判讀">判讀</h2>
<p>用 JetStream 的 Leaf Node 做跨雲橋接、把 on-prem Faster Payments 機房跟雲端 cluster 連起來。揭露金融支付對端到端 latency 預算的硬要求逼出特定 broker 選型、不是「Kafka / SQS 通用化」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream stream 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS</a>（跨區對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/blog/how-form3-built-a-multi-cloud-low-latency-payments-service-with-nats-io-jetstream">How Form3 Built a Multi-Cloud Low-Latency Payments Service with NATS JetStream (Synadia blog)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C35 Snap：GCP + KeyDB 在 multi-cloud 架構下的低延遲快取</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/</guid><description>&lt;p>這個案例的核心責任是補強 GCP cache 維度、並揭示 multi-cloud 架構的隱性 latency 議題。Snap（Snapchat 母公司、日活 4 億 +）2011 年從零起就在 GCP 上、是雲原生最早期客戶之一、但近年走 multi-cloud（GCP + AWS）。這個架構引出「跨 cloud cache latency 怎麼處理」的工程議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Snap 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud&lt;/a>、&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap TPU recommendation&lt;/a>）：&lt;/p>
&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>4 億 + DAU、年增 18% YoY&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開始在 GCP 時間&lt;/td>
 &lt;td>2011 年（產品早期）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-cloud cache 方案&lt;/td>
 &lt;td>GCP 上部署 KeyDB cluster 減少 cross-cloud latency&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML training&lt;/td>
 &lt;td>TPU（vs GPU 吞吐高 67%、成本低 52%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>安全框架&lt;/td>
 &lt;td>BeyondCorp Enterprise（Zero Trust）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵架構決策：在 &lt;em>GCP&lt;/em> 上部署 KeyDB（Redis fork、multi-threaded）作為 cache layer、減少 cross-cloud latency。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Snap 案例揭露三個 multi-cloud 容量設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>跨 cloud latency 是隱性容量瓶頸&lt;/strong>：當 application 在 AWS、cache 在 GCP（或反之）、每個 cache lookup 都吃跨 cloud 網路 latency（通常 5-30ms、視 region pair 而定）。對 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">Snap 這類「每次互動查多個 cache」&lt;/a> 的服務、5ms × 10 cache lookup = 50ms 額外 latency、用戶感受明顯。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;li>&lt;strong>KeyDB 是 Redis 的 multi-threaded 替代&lt;/strong>：Redis 7+ 之前是 single-threaded、單實例吞吐受限。KeyDB（Snap 等大型用戶採用）改成 multi-threaded、單實例 throughput 提升 5-10x、適合超高吞吐 cache 需求。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder ElastiCache&lt;/a> 的 cache layer 設計、但 Snap 規模更大要走專業 fork。&lt;/li>
&lt;li>&lt;strong>TPU vs GPU 是 ML training 的容量成本決策&lt;/strong>：Snap 算過 GPU 的「throughput -67% + cost +52%」就是 TPU 的反向 — TPU 的 throughput 高 67%、cost 低 52% — 對 ML-heavy 公司是巨大決策。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 的雲端硬體選型、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &amp;#43; 1.5 億商品、用 GCP Vertex AI Search &amp;#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre Vertex AI&lt;/a> 的 ML 容量規劃同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 GCP cache 維度、並揭示 multi-cloud 架構的隱性 latency 議題。Snap（Snapchat 母公司、日活 4 億 +）2011 年從零起就在 GCP 上、是雲原生最早期客戶之一、但近年走 multi-cloud（GCP + AWS）。這個架構引出「跨 cloud cache latency 怎麼處理」的工程議題。</p>
<h2 id="觀察">觀察</h2>
<p>Snap 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud</a>、<a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap TPU recommendation</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶基礎</td>
          <td>4 億 + DAU、年增 18% YoY</td>
      </tr>
      <tr>
          <td>開始在 GCP 時間</td>
          <td>2011 年（產品早期）</td>
      </tr>
      <tr>
          <td>Multi-cloud cache 方案</td>
          <td>GCP 上部署 KeyDB cluster 減少 cross-cloud latency</td>
      </tr>
      <tr>
          <td>ML training</td>
          <td>TPU（vs GPU 吞吐高 67%、成本低 52%）</td>
      </tr>
      <tr>
          <td>安全框架</td>
          <td>BeyondCorp Enterprise（Zero Trust）</td>
      </tr>
  </tbody>
</table>
<p>關鍵架構決策：在 <em>GCP</em> 上部署 KeyDB（Redis fork、multi-threaded）作為 cache layer、減少 cross-cloud latency。</p>
<h2 id="判讀">判讀</h2>
<p>Snap 案例揭露三個 multi-cloud 容量設計的工程重點。</p>
<ol>
<li><strong>跨 cloud latency 是隱性容量瓶頸</strong>：當 application 在 AWS、cache 在 GCP（或反之）、每個 cache lookup 都吃跨 cloud 網路 latency（通常 5-30ms、視 region pair 而定）。對 <a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">Snap 這類「每次互動查多個 cache」</a> 的服務、5ms × 10 cache lookup = 50ms 額外 latency、用戶感受明顯。對應 <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
<li><strong>KeyDB 是 Redis 的 multi-threaded 替代</strong>：Redis 7+ 之前是 single-threaded、單實例吞吐受限。KeyDB（Snap 等大型用戶採用）改成 multi-threaded、單實例 throughput 提升 5-10x、適合超高吞吐 cache 需求。對應 <a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder ElastiCache</a> 的 cache layer 設計、但 Snap 規模更大要走專業 fork。</li>
<li><strong>TPU vs GPU 是 ML training 的容量成本決策</strong>：Snap 算過 GPU 的「throughput -67% + cost +52%」就是 TPU 的反向 — TPU 的 throughput 高 67%、cost 低 52% — 對 ML-heavy 公司是巨大決策。對應 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的雲端硬體選型、跟 <a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre Vertex AI</a> 的 ML 容量規劃同類。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>KeyDB 是 <em>fork-based</em> 軟體、有 vendor lock-in 風險（Snap 大規模採用後、KeyDB 公司被收購、未來 fork 走向不確定）</li>
<li>TPU 是 <em>Google 專屬硬體</em>、不能在其他 cloud 用、是 vendor lock-in 來源</li>
<li>「年增 18%」是用戶數、不是流量。流量成長通常超過用戶成長（per-user engagement 上升）</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Multi-cloud 架構優先把 cache 跟 application 放同一 cloud</strong>：跨 cloud 的不該是 cache lookup（高頻、低 latency 容忍）、應該是 batch sync（低頻、高 latency 容忍）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的部署策略。</li>
<li><strong>Redis 規模化遇到 single-threaded 限制時的選項</strong>：
<ul>
<li>拆 cluster（多個 Redis instance）— 應用層分散 key</li>
<li>換 KeyDB / Dragonfly（multi-threaded fork）</li>
<li>換 Redis 7+ I/O thread（保留 protocol）</li>
<li>換 Memcached（multi-threaded、但功能少）</li>
</ul>
</li>
<li><strong>ML training infrastructure 選型按 throughput / cost 而非品牌</strong>：GPU vs TPU vs Trainium 不是「哪家好」、是「在 <em>本 workload</em> 上哪個划算」。要實測 benchmark、不是看 vendor marketing。</li>
<li><strong>跨 cloud 部署的「資料引力」</strong>：data 在哪、application 通常會被 data 吸過去。Snap 把 cache 放 GCP 是因為 production data 在 GCP — 想搬 cache 到 AWS 同時要搬 data、成本高。</li>
</ol>
<p>跨平台等效：AWS ElastiCache + Cassandra / DynamoDB Global Tables、Azure Cache for Redis + Cosmos DB 都可實作 multi-region cache 但 single-cloud 內。multi-cloud cache 通常要自管（自管 KeyDB / Dragonfly / Redis Cluster）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 cache 案例 → <a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder ElastiCache</a> / <a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi ML feature store</a></li>
<li>想設計 multi-cloud cache → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a></li>
<li>想做 ML training 容量規劃 → <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界</a> + <a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre</a></li>
<li>想理解 cross-cloud latency → <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud to reduce cross-cloud latency</a></li>
<li><a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap Inc. uses Google Cloud TPU for deep learning recommendation models</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/snap-maintains-uptime-with-mcs-from-google-cloud/">Snap maintains uptime with MCS from Google Cloud</a></li>
<li><a href="https://cloud.google.com/blog/products/identity-security/why-snap-chose-beyondcorp-enterprise-to-build-a-durable-zero-trust-framework">Why Snap chose BeyondCorp Enterprise</a></li>
</ul>
]]></content:encoded></item><item><title>9.C36 Coinbase：MongoDB 撐 Ruby 單體 + 1.5M reads/sec identity 服務</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/</guid><description>&lt;p>這個案例的核心責任是說明「document database 在大規模 OLTP 場景如何撐住」。Coinbase 從 Ruby on Rails 單體 + MongoDB 起家、八年後仍保留 MongoDB 作為主資料層、並把 connection pooling、ML 預測擴容、cache + freshness token 都疊在 document model 上。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365&lt;/a> 對照 — Microsoft 365 走「遷出 MongoDB、保留 document API」、Coinbase 走「保留 MongoDB、補周邊工具」。兩條路徑都揭露 MongoDB 在 production 主角位置會遇到什麼壓力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Coinbase MongoDB 平台的關鍵數字（引自 &lt;a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase Engineering Blog&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Users 服務尖峰讀取&lt;/td>
 &lt;td>1.5M reads / sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deploy 時 MongoDB 連線尖峰&lt;/td>
 &lt;td>~60K connections / minute（單 cluster）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mongobetween 後連線降幅&lt;/td>
 &lt;td>30K → ~2K（一個量級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MongoDB cluster 數量&lt;/td>
 &lt;td>many clusters（多服務 federated）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>加密貨幣 surge 擴容時間&lt;/td>
 &lt;td>70 分鐘 → 25 分鐘（-64%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML 預測擴容領先窗&lt;/td>
 &lt;td>60 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cache 命中後跳過 DB&lt;/td>
 &lt;td>是（Memcached query-cache）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（主資料層）、DynamoDB（部分 workload 的 federated store）、Memcached（query result cache）、自研 mongobetween proxy（連線多工）、Ruby on Rails 單體 + 多個 Fragment APIs、ML 預測模型驅動 cluster auto-scaling。&lt;/p>
&lt;p>關鍵負載形狀：「加密貨幣價格突發 + 用戶交易需求湧入」雙峰疊加。價格 alert 觸發 read 爆量（users / portfolio 查詢）、下單觸發 write 爆量（order book / wallet 寫入）。兩種峰值不像 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &amp;#43;50% 不影響延遲">9.C4 DraftKings&lt;/a> 的 Super Bowl 事件型可預測、是隨外部市場波動的 &lt;em>low-latency-sustained 中夾雜 surge&lt;/em>。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Coinbase MongoDB 的工程選擇揭露三個 document database 在 production 主角位置的設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>MongoDB + Ruby 連線爆炸需要外部 connection pool&lt;/strong>：CRuby 因為 GVL 必須每 CPU core 起一個 process、blue-green 部署期間 instance 數量 ×2、連線數隨之 ×2、單一 cluster 看到 60K 連線/分鐘。原生 MongoDB driver 沒有跨 process 的 connection pool — 跟 PostgreSQL 走 pgbouncer 是同樣需求、所以 Coinbase 自建 &lt;a href="https://github.com/coinbase/mongobetween">mongobetween&lt;/a> 做多工。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取&lt;/a> 的 connection storm 問題、document database 不會自動解決、要主動補工具。&lt;/li>
&lt;li>&lt;strong>document model 撐 1.5M reads/sec 靠 cache + freshness token&lt;/strong>：直接打 MongoDB 不可能撐 1.5M reads/sec — Coinbase 在 users 服務前面加 Memcached query cache、單 document query 先查 cache。但 cache + write 會有一致性問題、所以引入 OCC version 跟 &lt;em>freshness token&lt;/em>：write 成功後給 client 一個 token、client 之後 read 帶 token、server 保證返回的資料版本 ≥ token、必要時 bypass cache 直接打 DB。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的 read-after-write 設計。&lt;/li>
&lt;li>&lt;strong>加密貨幣 surge 用 ML 預測、不靠 reactive scaling&lt;/strong>：cluster 擴容要 70 分鐘、傳統 CPU / queue 觸發的 reactive scaling 在 surge 開始時才動、來不及。Coinbase 訓練 ML 模型分析價格資料、提前 60 分鐘預測流量、預先擴容。把擴容時間從 70 分鐘壓到 25 分鐘是 trigger 提前、不是擴容本身變快。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的 predictive scaling。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「document database 在大規模 OLTP 場景如何撐住」。Coinbase 從 Ruby on Rails 單體 + MongoDB 起家、八年後仍保留 MongoDB 作為主資料層、並把 connection pooling、ML 預測擴容、cache + freshness token 都疊在 document model 上。跟 <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a> 對照 — Microsoft 365 走「遷出 MongoDB、保留 document API」、Coinbase 走「保留 MongoDB、補周邊工具」。兩條路徑都揭露 MongoDB 在 production 主角位置會遇到什麼壓力。</p>
<h2 id="觀察">觀察</h2>
<p>Coinbase MongoDB 平台的關鍵數字（引自 <a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase Engineering Blog</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Users 服務尖峰讀取</td>
          <td>1.5M reads / sec</td>
      </tr>
      <tr>
          <td>Deploy 時 MongoDB 連線尖峰</td>
          <td>~60K connections / minute（單 cluster）</td>
      </tr>
      <tr>
          <td>mongobetween 後連線降幅</td>
          <td>30K → ~2K（一個量級）</td>
      </tr>
      <tr>
          <td>MongoDB cluster 數量</td>
          <td>many clusters（多服務 federated）</td>
      </tr>
      <tr>
          <td>加密貨幣 surge 擴容時間</td>
          <td>70 分鐘 → 25 分鐘（-64%）</td>
      </tr>
      <tr>
          <td>ML 預測擴容領先窗</td>
          <td>60 分鐘</td>
      </tr>
      <tr>
          <td>Cache 命中後跳過 DB</td>
          <td>是（Memcached query-cache）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（主資料層）、DynamoDB（部分 workload 的 federated store）、Memcached（query result cache）、自研 mongobetween proxy（連線多工）、Ruby on Rails 單體 + 多個 Fragment APIs、ML 預測模型驅動 cluster auto-scaling。</p>
<p>關鍵負載形狀：「加密貨幣價格突發 + 用戶交易需求湧入」雙峰疊加。價格 alert 觸發 read 爆量（users / portfolio 查詢）、下單觸發 write 爆量（order book / wallet 寫入）。兩種峰值不像 <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a> 的 Super Bowl 事件型可預測、是隨外部市場波動的 <em>low-latency-sustained 中夾雜 surge</em>。</p>
<h2 id="判讀">判讀</h2>
<p>Coinbase MongoDB 的工程選擇揭露三個 document database 在 production 主角位置的設計重點。</p>
<ol>
<li><strong>MongoDB + Ruby 連線爆炸需要外部 connection pool</strong>：CRuby 因為 GVL 必須每 CPU core 起一個 process、blue-green 部署期間 instance 數量 ×2、連線數隨之 ×2、單一 cluster 看到 60K 連線/分鐘。原生 MongoDB driver 沒有跨 process 的 connection pool — 跟 PostgreSQL 走 pgbouncer 是同樣需求、所以 Coinbase 自建 <a href="https://github.com/coinbase/mongobetween">mongobetween</a> 做多工。對應 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> 的 connection storm 問題、document database 不會自動解決、要主動補工具。</li>
<li><strong>document model 撐 1.5M reads/sec 靠 cache + freshness token</strong>：直接打 MongoDB 不可能撐 1.5M reads/sec — Coinbase 在 users 服務前面加 Memcached query cache、單 document query 先查 cache。但 cache + write 會有一致性問題、所以引入 OCC version 跟 <em>freshness token</em>：write 成功後給 client 一個 token、client 之後 read 帶 token、server 保證返回的資料版本 ≥ token、必要時 bypass cache 直接打 DB。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的 read-after-write 設計。</li>
<li><strong>加密貨幣 surge 用 ML 預測、不靠 reactive scaling</strong>：cluster 擴容要 70 分鐘、傳統 CPU / queue 觸發的 reactive scaling 在 surge 開始時才動、來不及。Coinbase 訓練 ML 模型分析價格資料、提前 60 分鐘預測流量、預先擴容。把擴容時間從 70 分鐘壓到 25 分鐘是 trigger 提前、不是擴容本身變快。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 predictive scaling。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「1.5M reads/sec」是 users 服務 <em>加上 cache</em> 的數字、不是 MongoDB cluster 純讀取數字。讀案例時要區分「應用層觀察到」跟「DB 層實際承擔」。</li>
<li>mongobetween 是 Coinbase 特殊環境（Ruby + GVL + blue-green）的產物。Go / Java / Node.js 應用因為原生支援連線多工、通常不需要這層 proxy。</li>
<li>ML 預測有 false positive / false negative — 預測錯時要嘛浪費容量、要嘛 surge 真來時擋不住。Coinbase 沒揭露準確率、所以仍保留 reactive scaling 作為 safety net。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>document database 撐大規模 OLTP 要主動補 connection pool</strong>：MongoDB 原生 connection 模式對「process 數多 + deploy 重」的環境會爆。應用層或 sidecar proxy 做多工是基線設計。對應 <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a>。</li>
<li><strong>freshness token 是 read-after-write 一致性的可重用模式</strong>：比 strong consistency（性能差）跟 eventually consistent（read 不到剛寫的）更精細的中間路徑。token 機制可以推廣到任何「主要 eventually consistent、少數 read 要求最新」的場景。</li>
<li><strong>predictive scaling 適用於「外部訊號可預測流量」的服務</strong>：加密貨幣價格、賽事行程、票務開賣時間都是外部訊號。比 reactive scaling 早一個擴容週期出手。對應 <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> 的 AI 預測式擴容。</li>
<li><strong>federated DB（MongoDB + DynamoDB）按 workload 分流</strong>：document-shaped 用 MongoDB、access pattern 固定的 KV 用 DynamoDB。不是「全用 MongoDB」也不是「全遷 DynamoDB」、是按 workload 形狀分。對應 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的多 DB 整合反例（Netflix 走整合方向、Coinbase 走 federated）。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS：MongoDB Atlas + ElastiCache + DynamoDB（Coinbase 配置）</li>
<li>GCP：MongoDB Atlas on GCP + Memorystore + Firestore（document API）</li>
<li>Azure：Cosmos DB MongoDB API + Cache for Redis、不需要 Atlas</li>
<li>mongobetween 風格的 proxy：PostgreSQL 走 pgbouncer / pgcat、MongoDB 走 mongobetween / mongoproxy</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 MongoDB 大規模 production → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a></li>
<li>想做 read-after-write 一致性設計 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想做 predictive scaling → <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想對照 MongoDB 遷出 / 保留決策 → <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB MongoDB API）</li>
<li>想理解 connection storm 問題 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想深入 connection / proxy 治理與 cache 層 → <a href="/blog/backend/01-database/vendors/mongodb/connection-management-and-cache-layer/" data-link-title="MongoDB Connection Management and Cache Layer：driver × 部署模型 × cache × predictive scaling" data-link-desc="MongoDB 大規模 OLTP 撞牆不是單一 driver 議題、是 driver × 部署模型 × cache × scaling trigger 三層協作；含 Coinbase mongobetween / freshness token / ML 預測擴容三件套 &#43; 適用範圍紀律">MongoDB connection 管理與 cache 層</a></li>
<li>想做 replica set 讀寫分離設計 → <a href="/blog/backend/01-database/vendors/mongodb/replica-set-read-preference/" data-link-title="MongoDB Replica Set Read Preference：DB 層 causal session vs cache 層 freshness token" data-link-desc="MongoDB read preference 五擇一 &#43; read concern &#43; causal consistency session 機制；DB 層機制解 cluster 內 read-your-own-write、cache 層 freshness token 解跨層 read-after-write、大規模 OLTP 必須兩層合用">MongoDB replica set read preference</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase：Scaling connections with Ruby and MongoDB</a></li>
<li><a href="https://www.coinbase.com/blog/scaling-identity-how-coinbase-serves-1.5M-reads-second">Coinbase：Scaling Identity - How Coinbase Serves 1.5M Reads/Second</a></li>
<li><a href="https://www.coinbase.com/blog/how-we-do-mongodb-migrations-at-coinbase">Coinbase：How We Do MongoDB Migrations at Coinbase</a></li>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study：Coinbase Decreases Scaling Time</a></li>
<li><a href="https://github.com/coinbase/mongobetween">mongobetween GitHub repository</a></li>
</ul>
]]></content:encoded></item><item><title>3.C36 Intelecy：工業 IoT 即時感測 + 多租戶</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/</guid><description>&lt;p>這個案例的核心責任是說明 edge gateway 從本地 KV 演進到 JetStream 的決策訊號。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Intelecy 在工廠端 gateway 接「數萬個 sensor」、要求 &amp;lt; 2 秒往返延遲做即時 ML 推論、需要多租戶安全隔離與雲端無鎖定方案。Gateway 把 process data 寫進 Synadia Cloud topic。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>從 BoltDB 本地快取 → JetStream 持久化的演進、揭露「無 durable layer 時 edge gateway 自己要做存儲、加 JetStream 後可放掉本地 BoltDB」的決策訊號。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：JetStream stream 設計 / Subject-based ACL + 多租戶（sensor 隔離）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &amp;#43; JetStream &amp;#43; KV &amp;#43; Object Store。">3.C37 MachineMetrics&lt;/a>（同類對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/blog/how-intelecy-optimizes-factory-processes-with-nats-ngs-and-jetstream">How Intelecy Optimizes Factory Processes with NATS, NGS and JetStream&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 edge gateway 從本地 KV 演進到 JetStream 的決策訊號。</p>
<h2 id="觀察">觀察</h2>
<p>Intelecy 在工廠端 gateway 接「數萬個 sensor」、要求 &lt; 2 秒往返延遲做即時 ML 推論、需要多租戶安全隔離與雲端無鎖定方案。Gateway 把 process data 寫進 Synadia Cloud topic。</p>
<h2 id="判讀">判讀</h2>
<p>從 BoltDB 本地快取 → JetStream 持久化的演進、揭露「無 durable layer 時 edge gateway 自己要做存儲、加 JetStream 後可放掉本地 BoltDB」的決策訊號。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：JetStream stream 設計 / Subject-based ACL + 多租戶（sensor 隔離）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a>（同類對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/blog/how-intelecy-optimizes-factory-processes-with-nats-ngs-and-jetstream">How Intelecy Optimizes Factory Processes with NATS, NGS and JetStream</a></li>
</ul>
]]></content:encoded></item><item><title>9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/</guid><description>&lt;p>這個案例的核心責任是說明「從自管 MongoDB 遷到 Atlas managed」這條路徑的工程與成本對照。Forbes 自 2011 年起用 MongoDB 重寫 CMS、2020 年把 production 遷到 Atlas on Google Cloud、保留同一個 document model、轉移 DBA 責任跟跨雲彈性。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 的「跨 DB 種類遷移」對照 — Forbes 是 &lt;em>同 DB、換託管模式&lt;/em>、不需要重寫 schema 跟 access pattern。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Forbes 遷移到 MongoDB Atlas on Google Cloud 的關鍵數字（引自 &lt;a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Google Cloud Blog&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">MongoDB customer case study&lt;/a>）：&lt;/p>
&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>120M（2020 年 5 月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build 時間&lt;/td>
 &lt;td>25 分鐘 → 9 分鐘（-64%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Release 頻率提升&lt;/td>
 &lt;td>2x – 10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數量&lt;/td>
 &lt;td>50+（GKE 上）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移耗時&lt;/td>
 &lt;td>6 個月&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DB 總體擁有成本降幅&lt;/td>
 &lt;td>-25%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>電子報訂閱量&lt;/td>
 &lt;td>+92%（2020 全年）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Atlas 可用 region&lt;/td>
 &lt;td>70+（跨 AWS / GCP / Azure）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CMS MongoDB 起用年&lt;/td>
 &lt;td>2011（首版 CMS 兩個月內交付）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（managed document DB）、Google Cloud Platform（基礎設施）、Google Kubernetes Engine（50+ 微服務編排）、Google App Engine（部分 serverless 應用）、自建中介 abstraction layer（API 隔離 schema 變動）。&lt;/p>
&lt;p>關鍵負載形狀：「文章 publish 後突然爆量」是新聞媒體常態 — 熱門報導、人物專訪、財經事件都會在分鐘內把單篇文章拉到百萬讀者。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的「賽事時段預期峰值」不同、Forbes 的爆量是事件驅動、難以精確預測、需要 Atlas auto-scaling 撐住臨時讀爆。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Forbes 的遷移選擇揭露三個「自管 → managed」路徑的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>同 DB 換託管模式比換 DB 種類風險低、但 ROI 也較窄&lt;/strong>：Forbes 6 個月完成遷移、保留同 document model、schema 不動、application 改動只在 connection string 跟運維邊界。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 從 TiDB 遷到 DynamoDB&lt;/a> 對照、後者要重新設計 access pattern、ROI 大但風險高。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema migration playbook：「換 DB」跟「換託管」是兩個不同議題、不要混為一談。&lt;/li>
&lt;li>&lt;strong>跨雲彈性的價值在規避未來鎖定、不是當下省成本&lt;/strong>：Atlas 提供 AWS / GCP / Azure 跨雲部署。Forbes 選 GCP 是當下決策、但 Atlas 的跨雲能力讓未來雲商選型不再綁定特定 vendor。這跟 DynamoDB（AWS only）、Cosmos DB（Azure only）、Spanner（GCP only）的單雲鎖定形成對照。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 vendor lock-in 評估。&lt;/li>
&lt;li>&lt;strong>Build 時間 25 → 9 分鐘 = 開發者效率改善、不是 DB 性能改善&lt;/strong>：Build 時間下降主因是 ephemeral test environment 用 Atlas API spin-up、不是 MongoDB query 變快。CMS 系統的 production read latency Atlas 跟自管 MongoDB 差距通常在 ±20% 內、真正贏的是「開發 / 部署 cycle 變短」。讀案例時要區分「開發者體驗 metric」跟「production 性能 metric」、兩者改善的杠桿完全不同。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「從自管 MongoDB 遷到 Atlas managed」這條路徑的工程與成本對照。Forbes 自 2011 年起用 MongoDB 重寫 CMS、2020 年把 production 遷到 Atlas on Google Cloud、保留同一個 document model、轉移 DBA 責任跟跨雲彈性。跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 的「跨 DB 種類遷移」對照 — Forbes 是 <em>同 DB、換託管模式</em>、不需要重寫 schema 跟 access pattern。</p>
<h2 id="觀察">觀察</h2>
<p>Forbes 遷移到 MongoDB Atlas on Google Cloud 的關鍵數字（引自 <a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Google Cloud Blog</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單月不重複訪客</td>
          <td>120M（2020 年 5 月）</td>
      </tr>
      <tr>
          <td>Build 時間</td>
          <td>25 分鐘 → 9 分鐘（-64%）</td>
      </tr>
      <tr>
          <td>Release 頻率提升</td>
          <td>2x – 10x</td>
      </tr>
      <tr>
          <td>微服務數量</td>
          <td>50+（GKE 上）</td>
      </tr>
      <tr>
          <td>遷移耗時</td>
          <td>6 個月</td>
      </tr>
      <tr>
          <td>DB 總體擁有成本降幅</td>
          <td>-25%</td>
      </tr>
      <tr>
          <td>電子報訂閱量</td>
          <td>+92%（2020 全年）</td>
      </tr>
      <tr>
          <td>Atlas 可用 region</td>
          <td>70+（跨 AWS / GCP / Azure）</td>
      </tr>
      <tr>
          <td>CMS MongoDB 起用年</td>
          <td>2011（首版 CMS 兩個月內交付）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（managed document DB）、Google Cloud Platform（基礎設施）、Google Kubernetes Engine（50+ 微服務編排）、Google App Engine（部分 serverless 應用）、自建中介 abstraction layer（API 隔離 schema 變動）。</p>
<p>關鍵負載形狀：「文章 publish 後突然爆量」是新聞媒體常態 — 熱門報導、人物專訪、財經事件都會在分鐘內把單篇文章拉到百萬讀者。這跟 <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL</a> 的「賽事時段預期峰值」不同、Forbes 的爆量是事件驅動、難以精確預測、需要 Atlas auto-scaling 撐住臨時讀爆。</p>
<h2 id="判讀">判讀</h2>
<p>Forbes 的遷移選擇揭露三個「自管 → managed」路徑的判讀重點。</p>
<ol>
<li><strong>同 DB 換託管模式比換 DB 種類風險低、但 ROI 也較窄</strong>：Forbes 6 個月完成遷移、保留同 document model、schema 不動、application 改動只在 connection string 跟運維邊界。這跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 從 TiDB 遷到 DynamoDB</a> 對照、後者要重新設計 access pattern、ROI 大但風險高。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema migration playbook：「換 DB」跟「換託管」是兩個不同議題、不要混為一談。</li>
<li><strong>跨雲彈性的價值在規避未來鎖定、不是當下省成本</strong>：Atlas 提供 AWS / GCP / Azure 跨雲部署。Forbes 選 GCP 是當下決策、但 Atlas 的跨雲能力讓未來雲商選型不再綁定特定 vendor。這跟 DynamoDB（AWS only）、Cosmos DB（Azure only）、Spanner（GCP only）的單雲鎖定形成對照。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor lock-in 評估。</li>
<li><strong>Build 時間 25 → 9 分鐘 = 開發者效率改善、不是 DB 性能改善</strong>：Build 時間下降主因是 ephemeral test environment 用 Atlas API spin-up、不是 MongoDB query 變快。CMS 系統的 production read latency Atlas 跟自管 MongoDB 差距通常在 ±20% 內、真正贏的是「開發 / 部署 cycle 變短」。讀案例時要區分「開發者體驗 metric」跟「production 性能 metric」、兩者改善的杠桿完全不同。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「25% TCO 降幅」是 <em>特定流量規模下</em> 的數字。Atlas managed 服務在小流量時 cost-per-GB 比自管低（不用養 DBA），但流量增長到一定規模後 self-hosted 反而便宜。Forbes 在 120M MAU 規模下選 managed 是合理判斷、但這個結論不是普適的。</li>
<li>「Build 25 → 9 分鐘」混合了「MongoDB Atlas API」、「GKE optimization」、「GCP CI/CD」三個變因。把全部歸功於 MongoDB Atlas 會誇大效益。</li>
<li>中介 abstraction layer 是 Forbes 主動加的設計、不是 Atlas 自帶。沒有這層 abstraction、schema 變動仍會直接打穿到所有 microservice、跨雲彈性也用不起來。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>自管 → managed 的遷移要先做 schema 跟 access pattern 盤點</strong>：確認沒有自管時的特殊 hack（自訂 plugin、特殊 storage engine、客製 oplog 處理）— 這些在 managed 服務上通常不支援。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
<li><strong>微服務 + abstraction layer 隔離 schema 變動</strong>：document database 的 schema flexibility 容易讓 production 出現 data inconsistency。中介 API 層把 schema 變動限制在 DB 邊界、microservice 看到的是穩定 API。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor 的 schema governance 段</a>。</li>
<li><strong>跨雲 managed 服務比單雲服務更適合長期不確定的雲商策略</strong>：Atlas（跨 AWS / GCP / Azure）vs DynamoDB / Cosmos DB / Spanner（單雲）的取捨。當雲商選擇尚未底定、跨雲服務的選項保留價值高。對應 <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB vendor page</a> 跟 <a href="/blog/backend/01-database/vendors/cosmosdb/" data-link-title="Azure Cosmos DB" data-link-desc="全球分散式 multi-model DB、5 個 consistency levels、Microsoft 自家 dogfood 證據">Cosmos DB vendor page</a> 對比。</li>
<li><strong>遷移時間表跟團隊規模耦合</strong>：Forbes 6 個月完成、團隊規模未揭露但顯然是中型團隊 + 多個 squad 並行。1-2 人團隊做同類遷移通常要 12+ 個月。對應 <a href="/blog/backend/01-database/large-scale-db-migration/" data-link-title="1.12 大規模 DB 遷移實戰" data-link-desc="跨 DB 遷移的 dual-write、[shadow read](/backend/knowledge-cards/shadow-read/)、cutover、rollback 流程 — 從實戰案例提煉的工程做法">01.12 大規模 DB 遷移實戰</a> 的時間估計。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>自管 MongoDB → MongoDB Atlas（同 DB、換託管）：Forbes、SEGA HARDlight 路徑</li>
<li>自管 MongoDB → DocumentDB（AWS 自研、API 部分相容）：較多應用層改動、跨雲彈性失去</li>
<li>自管 MongoDB → Cosmos DB MongoDB API（Azure）：<a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a> 路徑、有 RU 模型差異</li>
<li>自管 PostgreSQL → Aurora / Cloud SQL：對等遷移、但 RDB 跟 document DB 的 schema 治理議題不同</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 MongoDB 遷移到 Atlas → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想評估跨雲 vs 單雲 DB 取捨 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB vendor page</a> 對比段</li>
<li>想做 microservice + abstraction layer 設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想對照同類遷移 → <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB MongoDB API）/ <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a>（換 DB 種類）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Forbes migrates from self-managed MongoDB to MongoDB Atlas on Google Cloud</a></li>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">New CMS and developer environment cuts build times to just nine minutes for Forbes</a></li>
<li><a href="https://www.mongodb.com/resources/solutions/use-cases/forbes-cloud-migration-helps-worlds-biggest-media-brand-continue-standard-digital-innovation">Forbes：MongoDB Cloud Migration Helps World&rsquo;s Biggest Media Brand</a></li>
</ul>
]]></content:encoded></item><item><title>3.C37 MachineMetrics：邊緣到雲端工廠資料管線</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/</guid><description>&lt;p>這個案例的核心責任是說明工業 IoT 完整的 edge-to-cloud NATS 整合（Leaf Node + JetStream + KV + Object Store + Auth）。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>跨「數百個客戶廠區、數千台機台」的 Industrial IoT、單機產出最高 1000 Hz 採樣、工廠網路斷斷續續、Kinesis 等 cloud-only 工具無法跑在資源受限 edge 上。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 Leaf Node 做 hub-and-spoke 把邊緣設備串到雲端、Edge 端用 JetStream 做本地持久化（取代 SQLite）抵抗網路斷線、用 KV store 做 config / 短期 cache、Object Store 派發 WASM 模組、Decentralized Auth 隔離客戶。揭露「broker 的功能集合」決定它能不能取代多套 edge 工具。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream KV + Object Store / Subject-based ACL + 多租戶。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &amp;#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&amp;lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36 Intelecy&lt;/a>（同類對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/customer-stories/machinemetrics">MachineMetrics Customer Story (Synadia)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明工業 IoT 完整的 edge-to-cloud NATS 整合（Leaf Node + JetStream + KV + Object Store + Auth）。</p>
<h2 id="觀察">觀察</h2>
<p>跨「數百個客戶廠區、數千台機台」的 Industrial IoT、單機產出最高 1000 Hz 採樣、工廠網路斷斷續續、Kinesis 等 cloud-only 工具無法跑在資源受限 edge 上。</p>
<h2 id="判讀">判讀</h2>
<p>用 Leaf Node 做 hub-and-spoke 把邊緣設備串到雲端、Edge 端用 JetStream 做本地持久化（取代 SQLite）抵抗網路斷線、用 KV store 做 config / 短期 cache、Object Store 派發 WASM 模組、Decentralized Auth 隔離客戶。揭露「broker 的功能集合」決定它能不能取代多套 edge 工具。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream KV + Object Store / Subject-based ACL + 多租戶。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36 Intelecy</a>（同類對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/customer-stories/machinemetrics">MachineMetrics Customer Story (Synadia)</a></li>
</ul>
]]></content:encoded></item><item><title>9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/</guid><description>&lt;p>這個案例的核心責任是說明「IoT / telematics 高頻 sensor 寫入」如何套在 document model 上、以及 MongoDB Atlas 在 mission-critical（生命安全）服務中的角色。Toyota Connected 把車輛 sensor、緊急通報（SOS / 撞擊偵測）、駕駛資料都寫進 20 個 MongoDB Atlas database、用 event-driven microservice 處理。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB&lt;/a> 對照 — Amazon Ads 用 KV 撐極高吞吐、Toyota 用 document model 撐「形狀變化頻繁的 sensor signal」、兩條路徑反映不同的工作負載決策。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Toyota Connected 平台關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">AWS case study&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">MongoDB customer case study&lt;/a>）：&lt;/p>
&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>9M+（Toyota / Lexus 北美 Safety Connect）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>每月平台 transaction&lt;/td>
 &lt;td>18 Billion&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量擴展能力&lt;/td>
 &lt;td>18x usual 流量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>緊急訊號處理延遲&lt;/td>
 &lt;td>3 秒內到 safety agent&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性目標&lt;/td>
 &lt;td>99.99%（target、實測 99% 月達成）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MongoDB Atlas DB 數&lt;/td>
 &lt;td>20&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS 用量成長&lt;/td>
 &lt;td>3x（自 2018 啟動以來）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自管成本降幅&lt;/td>
 &lt;td>70-80%（serverless 架構整體）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>車載 sensor 種類&lt;/td>
 &lt;td>數百個（occupant、seatbelt、fuel、air quality）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（document store，20 databases）、AWS Lambda（serverless 處理事件）、Amazon Kinesis Data Streams（即時資料攝取）、CloudAMQP（非同步訊息）、Redis（hot cache）、Kubernetes（microservice 編排）。&lt;/p>
&lt;p>關鍵負載形狀：「車輛 sensor 持續低頻 + 緊急事件高優先低延遲」雙模式並存。&lt;/p>
&lt;ul>
&lt;li>&lt;em>持續模式&lt;/em>：900 萬車輛、每車數百 sensor、定期上報遙測資料。這是「sustained-growth + 高 throughput」的形狀、document model 比 wide-column 更適合 — 因為不同車型 / 不同年份的 sensor schema 不一樣、document 自然演進、不需要每加 sensor 就 ALTER TABLE。&lt;/li>
&lt;li>&lt;em>緊急模式&lt;/em>：SOS 按鈕、自動撞擊通報、車輛安全異常。這是 &lt;em>life-critical low-latency&lt;/em> — 3 秒內 sensor 訊號要從車輛到 agent 螢幕、含網路傳輸、event routing、microservice 處理、agent UI rendering。這個 budget 倒推回 MongoDB 寫入要求是 sub-100ms。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Toyota Connected 的 MongoDB 選擇揭露三個 IoT / telematics 工程決策的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>document model 適合「sensor schema 隨產品演進」的場景&lt;/strong>：車載 sensor 種類隨車型、年份、地區規範變化。RDB 走「每加 sensor 加 column」會讓 schema migration 變成發行週期的卡點；document model 走「polymorphic document」、新 sensor 只是新欄位、舊文件不需要 backfill。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page&lt;/a> 的 document shape 教學段。但這個彈性的成本是：production 必須做 schema governance（validation、版本欄位、application 層相容處理），否則「schema 自由」會變「production data inconsistency」。&lt;/li>
&lt;li>&lt;strong>20 個 Atlas database 不是技術上限、是業務邊界切分&lt;/strong>：18 Billion transactions / 月 ÷ 30 天 ÷ 86400 秒 ≈ 7K transactions / sec。這個數字單一 MongoDB cluster 可以撐、不需要 20 個 DB。Toyota 切 20 個 DB 是按 &lt;em>microservice ownership&lt;/em> 跟 &lt;em>blast radius&lt;/em> — 每個 microservice 擁有自己的 DB、單一 DB 故障不會影響其他服務。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>、把「總吞吐」拆成「per-DB 邊界」。&lt;/li>
&lt;li>&lt;strong>99.99% target vs 99% 實測差距揭露 telematics 的可用性挑戰&lt;/strong>：99.99% 是 4 分鐘 / 月停機、99% 是 7.2 小時 / 月停機。差兩個 9 不是 MongoDB 自身可用性問題、是 &lt;em>end-to-end&lt;/em> 鏈路問題 — 車輛無線網路、cellular tower、AWS network、event bus、microservice、Atlas cluster 任一環節掉都會打掉可用性。MongoDB Atlas 自身的 SLA 通常是 99.95%、達到 99.99% 必須 multi-region + 跨雲冗餘。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%&lt;/a> 的多 region active-active 設計。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「IoT / telematics 高頻 sensor 寫入」如何套在 document model 上、以及 MongoDB Atlas 在 mission-critical（生命安全）服務中的角色。Toyota Connected 把車輛 sensor、緊急通報（SOS / 撞擊偵測）、駕駛資料都寫進 20 個 MongoDB Atlas database、用 event-driven microservice 處理。跟 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB</a> 對照 — Amazon Ads 用 KV 撐極高吞吐、Toyota 用 document model 撐「形狀變化頻繁的 sensor signal」、兩條路徑反映不同的工作負載決策。</p>
<h2 id="觀察">觀察</h2>
<p>Toyota Connected 平台關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">AWS case study</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>服務涵蓋車輛數</td>
          <td>9M+（Toyota / Lexus 北美 Safety Connect）</td>
      </tr>
      <tr>
          <td>每月平台 transaction</td>
          <td>18 Billion</td>
      </tr>
      <tr>
          <td>流量擴展能力</td>
          <td>18x usual 流量</td>
      </tr>
      <tr>
          <td>緊急訊號處理延遲</td>
          <td>3 秒內到 safety agent</td>
      </tr>
      <tr>
          <td>可用性目標</td>
          <td>99.99%（target、實測 99% 月達成）</td>
      </tr>
      <tr>
          <td>MongoDB Atlas DB 數</td>
          <td>20</td>
      </tr>
      <tr>
          <td>AWS 用量成長</td>
          <td>3x（自 2018 啟動以來）</td>
      </tr>
      <tr>
          <td>自管成本降幅</td>
          <td>70-80%（serverless 架構整體）</td>
      </tr>
      <tr>
          <td>車載 sensor 種類</td>
          <td>數百個（occupant、seatbelt、fuel、air quality）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（document store，20 databases）、AWS Lambda（serverless 處理事件）、Amazon Kinesis Data Streams（即時資料攝取）、CloudAMQP（非同步訊息）、Redis（hot cache）、Kubernetes（microservice 編排）。</p>
<p>關鍵負載形狀：「車輛 sensor 持續低頻 + 緊急事件高優先低延遲」雙模式並存。</p>
<ul>
<li><em>持續模式</em>：900 萬車輛、每車數百 sensor、定期上報遙測資料。這是「sustained-growth + 高 throughput」的形狀、document model 比 wide-column 更適合 — 因為不同車型 / 不同年份的 sensor schema 不一樣、document 自然演進、不需要每加 sensor 就 ALTER TABLE。</li>
<li><em>緊急模式</em>：SOS 按鈕、自動撞擊通報、車輛安全異常。這是 <em>life-critical low-latency</em> — 3 秒內 sensor 訊號要從車輛到 agent 螢幕、含網路傳輸、event routing、microservice 處理、agent UI rendering。這個 budget 倒推回 MongoDB 寫入要求是 sub-100ms。</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Toyota Connected 的 MongoDB 選擇揭露三個 IoT / telematics 工程決策的判讀重點。</p>
<ol>
<li><strong>document model 適合「sensor schema 隨產品演進」的場景</strong>：車載 sensor 種類隨車型、年份、地區規範變化。RDB 走「每加 sensor 加 column」會讓 schema migration 變成發行週期的卡點；document model 走「polymorphic document」、新 sensor 只是新欄位、舊文件不需要 backfill。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> 的 document shape 教學段。但這個彈性的成本是：production 必須做 schema governance（validation、版本欄位、application 層相容處理），否則「schema 自由」會變「production data inconsistency」。</li>
<li><strong>20 個 Atlas database 不是技術上限、是業務邊界切分</strong>：18 Billion transactions / 月 ÷ 30 天 ÷ 86400 秒 ≈ 7K transactions / sec。這個數字單一 MongoDB cluster 可以撐、不需要 20 個 DB。Toyota 切 20 個 DB 是按 <em>microservice ownership</em> 跟 <em>blast radius</em> — 每個 microservice 擁有自己的 DB、單一 DB 故障不會影響其他服務。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>、把「總吞吐」拆成「per-DB 邊界」。</li>
<li><strong>99.99% target vs 99% 實測差距揭露 telematics 的可用性挑戰</strong>：99.99% 是 4 分鐘 / 月停機、99% 是 7.2 小時 / 月停機。差兩個 9 不是 MongoDB 自身可用性問題、是 <em>end-to-end</em> 鏈路問題 — 車輛無線網路、cellular tower、AWS network、event bus、microservice、Atlas cluster 任一環節掉都會打掉可用性。MongoDB Atlas 自身的 SLA 通常是 99.95%、達到 99.99% 必須 multi-region + 跨雲冗餘。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%</a> 的多 region active-active 設計。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「18 Billion transactions / 月」是 <em>平台所有服務</em> 加總、不是 MongoDB 單一 cluster 數字。MongoDB 只承擔其中需要 document storage 的部分、其他走 Lambda 直接處理或寫到 Kinesis。</li>
<li>「3 秒延遲到 agent」包含車載、無線、雲端、UI、agent 操作多個環節。MongoDB 在這個延遲鏈裡通常分到 100-500ms 預算、不是整個 3 秒。</li>
<li>MongoDB 6.0+ 有 time series collection 對 IoT 寫入有專屬優化。Toyota 揭露的 20 個 DB 沒明確說有沒有用 time series collection — 對 IoT 案例這是重要區分、但 case study 沒揭露。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>IoT 高頻 sensor 寫入考慮 MongoDB time series collection（6.0+）</strong>：比 regular collection 寫入吞吐高 3-5x、storage 壓縮率更好。專為 timestamp + metadata + measurement 三段式資料優化。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> 的容量規劃要點段。</li>
<li><strong>mission-critical IoT 系統要做 multi-region 跟多供應商備援</strong>：99.99% 不能只靠 MongoDB Atlas 本身、要靠 region 冗餘 + 多條 cellular network + 多個 event bus 路徑。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> 的 multi-region active-active。</li>
<li><strong>按 microservice ownership 切 MongoDB cluster、不要單一巨型 cluster</strong>：blast radius 邊界 = 業務邊界、不是「能不能撐」的問題。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>event-driven 處理 IoT 資料、不用 request-response</strong>：sensor 寫到 Kinesis / Kafka / event bus、microservice 從 stream 消費、寫進 MongoDB。這條 path 避免「sensor 寫不進去 DB 就 retry storm」的問題。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a>。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS：MongoDB Atlas + Kinesis + Lambda（Toyota 配置）</li>
<li>GCP：MongoDB Atlas on GCP + Pub/Sub + Cloud Functions、或 Firestore + Pub/Sub（document API native）</li>
<li>Azure：Cosmos DB MongoDB API + Event Hubs + Azure Functions</li>
<li>跨雲：MongoDB Atlas 是 IoT 平台保留跨雲彈性的少數選項</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 IoT / telematics 資料層 → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a></li>
<li>想做 multi-region 高可用性 → <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%</a></li>
<li>想對照不同 IoT 資料層選擇 → <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB</a>（KV）/ <a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay</a>（高頻訊息）</li>
<li>想理解 event-driven IoT 架構 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a></li>
<li>想做 IoT 寫入吞吐的 shard key 選型 → <a href="/blog/backend/01-database/vendors/mongodb/shard-key-selection/" data-link-title="MongoDB Shard Key Selection：hashed vs ranged、單 cluster 切 shard vs 多 cluster 切 blast radius" data-link-desc="MongoDB sharded cluster shard key 選型（hashed / ranged / compound）、單 cluster 分 shard vs 多 cluster 分 blast radius 對照、跟 DynamoDB / Cosmos DB partition key 可逆性的跨 vendor 紀律">MongoDB shard key 選型</a></li>
<li>想規劃 telemetry schema design → <a href="/blog/backend/01-database/vendors/mongodb/schema-design-pattern/" data-link-title="MongoDB Schema Design Pattern：contract layer 在哪 vs embedded / reference" data-link-desc="MongoDB document schema 真正的 production 議題不是 embedded vs reference 二選一、是 schema contract 該放 DB 層 validator 還是 app 層 abstraction；含 Toyota polymorphic governance、Forbes abstraction layer、time-series collection 邊界">MongoDB schema design pattern</a></li>
<li>想處理 IoT 高 client 數的 connection storm → <a href="/blog/backend/01-database/vendors/mongodb/connection-management-and-cache-layer/" data-link-title="MongoDB Connection Management and Cache Layer：driver × 部署模型 × cache × predictive scaling" data-link-desc="MongoDB 大規模 OLTP 撞牆不是單一 driver 議題、是 driver × 部署模型 × cache × scaling trigger 三層協作；含 Coinbase mongobetween / freshness token / ML 預測擴容三件套 &#43; 適用範圍紀律">MongoDB connection 管理與 cache 層</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">Toyota Connected Aims For At Least 99.99% Availability With MongoDB Assistance</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">Toyota Connected Reimagines Mobility on AWS</a></li>
<li><a href="https://digitalcxo.com/article/mongodb-aws-help-toyota-connected-move-past-legacy-database-challenges/">MongoDB, AWS Help Toyota Connected Move Past Legacy Database Challenges</a></li>
<li><a href="https://www.just-auto.com/news/toyota-connected-hails-efficiencies-from-migration-of-data-services-to-mongodb-atlas/">Toyota Connected hails efficiencies from migration of data services to MongoDB Atlas</a></li>
<li><a href="https://www.mongodb.com/company/blog/innovation/data-modeling-strategies-connected-vehicle-signal-data-in-mongodb">Data Modeling Strategies For Connected Vehicle Signal Data In MongoDB</a></li>
</ul>
]]></content:encoded></item><item><title>3.C38 Clarifai：NATS Streaming ML 平台非同步任務</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/</guid><description>&lt;p>這個案例的核心責任是說明 NATS Streaming（JetStream 前身）的 queue group + at-least-once 在 ML worker pool 的角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Clarifai 做 custom model 訓練、任務從幾秒到幾分鐘、原本同步呼叫遇到 rolling deployment 會掉訊息。三週內把一個服務遷到 NATS、5 個月內擴展到 5 個服務、每日 100k+ 訊息、100% uptime。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 NATS Streaming 的 at-least-once delivery + queue subscription group 做 worker pool、每個微服務連到三個獨立 NATS Streaming 實例做 fanout 隔離。揭露 ML 任務的長尾處理時間特別需要 at-least-once + redelivery、不能容忍 rolling deploy 掉訊息。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：JetStream consumer 設計（NATS Streaming 是前身）/ Queue groups。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/how-clarifai-uses-nats-and-kubernetes-for-machine-learning/">How Clarifai Uses NATS and Kubernetes for Machine Learning&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 NATS Streaming（JetStream 前身）的 queue group + at-least-once 在 ML worker pool 的角色。</p>
<h2 id="觀察">觀察</h2>
<p>Clarifai 做 custom model 訓練、任務從幾秒到幾分鐘、原本同步呼叫遇到 rolling deployment 會掉訊息。三週內把一個服務遷到 NATS、5 個月內擴展到 5 個服務、每日 100k+ 訊息、100% uptime。</p>
<h2 id="判讀">判讀</h2>
<p>用 NATS Streaming 的 at-least-once delivery + queue subscription group 做 worker pool、每個微服務連到三個獨立 NATS Streaming 實例做 fanout 隔離。揭露 ML 任務的長尾處理時間特別需要 at-least-once + redelivery、不能容忍 rolling deploy 掉訊息。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：JetStream consumer 設計（NATS Streaming 是前身）/ Queue groups。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/how-clarifai-uses-nats-and-kubernetes-for-machine-learning/">How Clarifai Uses NATS and Kubernetes for Machine Learning</a></li>
</ul>
]]></content:encoded></item><item><title>9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/</guid><description>&lt;p>這個案例的核心責任是說明「single-primary OLTP 撞到寫入天花板」如何用 distributed SQL 拆解。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &amp;#43;50% 不影響延遲">9.C4 DraftKings&lt;/a> 對比 — DraftKings 在 Aurora 上靠「業務切 200 個獨立 cluster」橫向擴展、DoorDash 是「保留 PostgreSQL wire 介面、但底層換成多主寫入的 CockroachDB」。兩條路徑都在解「Aurora 單主寫入容量上限」、走法不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>DoorDash 從 Aurora Postgres 遷到 CockroachDB 的關鍵敘述（引自 &lt;a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB&lt;/a> / &lt;a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">The New Stack 報導&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>2020-04-17 高峰 QPS&lt;/td>
 &lt;td>&amp;gt; 1.636 million QPS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件結果&lt;/td>
 &lt;td>multi-hour outage&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件背景&lt;/td>
 &lt;td>疫情封鎖、外送需求暴增&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移啟動&lt;/td>
 &lt;td>事件後幾週、先把 table 從主 cluster 拆出&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第一階段移轉量&lt;/td>
 &lt;td>一個月內把 dozens of tables 拆到獨立 Aurora cluster&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第二階段&lt;/td>
 &lt;td>自動化工具把 Aurora Postgres → CockroachDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>後續結果&lt;/td>
 &lt;td>跑更多 cluster、incident alert volume 反而下降&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Aurora Postgres（遷移前主要 OLTP）、CockroachDB self-hosted、自製 table extraction tool、自製 lossless migration pipeline。&lt;/p>
&lt;p>關鍵負載形狀：DoorDash 是 &lt;em>規模化外送平台&lt;/em> — 訂單、Dasher 派遣、餐廳 menu、新業務（grocery / convenience）並存。寫入壓力來自訂單成立、status 變更、地圖位置更新等多種 hot write path。2020 疫情前流量已大、疫情後再翻倍、且高峰集中在週末晚餐 / 週日早午餐時段。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>DoorDash 的工程選擇揭露三個 OLTP 寫入容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Aurora 的「single-primary 寫入」是規模化的天花板&lt;/strong>：Aurora 把 storage 跟 compute 分離、read replica 容易擴、&lt;em>但寫入仍走唯一 primary&lt;/em>。1.636 M QPS 不是均勻分佈、是 hot table 集中寫爆。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取&lt;/a> 的寫入容量規劃。CockroachDB 改成 Raft per range、每個 node 都能服務寫入、容量隨節點線性擴。&lt;/li>
&lt;li>&lt;strong>Migration 工具自製是先決條件、不是 nice-to-have&lt;/strong>：DoorDash 沒「一次性遷整套」、而是先寫工具把 table 從主 cluster 拆到獨立 Aurora cluster（紓壓）、再寫第二套工具把 Aurora → CockroachDB（換引擎）。兩階段都要 &lt;em>lossless&lt;/em> + &lt;em>可回退&lt;/em>。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a> 的「先建工具、再遷資料」原則。&lt;/li>
&lt;li>&lt;strong>Cluster 數量增加、alert volume 卻下降&lt;/strong>：直覺反過來、cluster 多 = 維運面變大、應該更多 alert。但每個 CockroachDB cluster 內建 Raft 自動容錯、單節點 fail 不會 page on-call、Aurora 時代的「primary failover alert」消失。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的「告警 surface 設計」與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06.x reliability&lt;/a> 的 graceful degradation。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：1.636 M QPS 是 &lt;em>主 cluster 峰值&lt;/em>、不是「DoorDash 全部寫入 QPS」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster」。讀案例時不要把這個數字當成「CockroachDB 撐 1.6 M QPS」的證據、它是 &lt;em>Aurora 在那個時間點撞牆的痛點&lt;/em>。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「single-primary OLTP 撞到寫入天花板」如何用 distributed SQL 拆解。跟 <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a> 對比 — DraftKings 在 Aurora 上靠「業務切 200 個獨立 cluster」橫向擴展、DoorDash 是「保留 PostgreSQL wire 介面、但底層換成多主寫入的 CockroachDB」。兩條路徑都在解「Aurora 單主寫入容量上限」、走法不同。</p>
<h2 id="觀察">觀察</h2>
<p>DoorDash 從 Aurora Postgres 遷到 CockroachDB 的關鍵敘述（引自 <a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB</a> / <a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">The New Stack 報導</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2020-04-17 高峰 QPS</td>
          <td>&gt; 1.636 million QPS</td>
      </tr>
      <tr>
          <td>事件結果</td>
          <td>multi-hour outage</td>
      </tr>
      <tr>
          <td>事件背景</td>
          <td>疫情封鎖、外送需求暴增</td>
      </tr>
      <tr>
          <td>遷移啟動</td>
          <td>事件後幾週、先把 table 從主 cluster 拆出</td>
      </tr>
      <tr>
          <td>第一階段移轉量</td>
          <td>一個月內把 dozens of tables 拆到獨立 Aurora cluster</td>
      </tr>
      <tr>
          <td>第二階段</td>
          <td>自動化工具把 Aurora Postgres → CockroachDB</td>
      </tr>
      <tr>
          <td>後續結果</td>
          <td>跑更多 cluster、incident alert volume 反而下降</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Aurora Postgres（遷移前主要 OLTP）、CockroachDB self-hosted、自製 table extraction tool、自製 lossless migration pipeline。</p>
<p>關鍵負載形狀：DoorDash 是 <em>規模化外送平台</em> — 訂單、Dasher 派遣、餐廳 menu、新業務（grocery / convenience）並存。寫入壓力來自訂單成立、status 變更、地圖位置更新等多種 hot write path。2020 疫情前流量已大、疫情後再翻倍、且高峰集中在週末晚餐 / 週日早午餐時段。</p>
<h2 id="判讀">判讀</h2>
<p>DoorDash 的工程選擇揭露三個 OLTP 寫入容量設計重點。</p>
<ol>
<li><strong>Aurora 的「single-primary 寫入」是規模化的天花板</strong>：Aurora 把 storage 跟 compute 分離、read replica 容易擴、<em>但寫入仍走唯一 primary</em>。1.636 M QPS 不是均勻分佈、是 hot table 集中寫爆。對應 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> 的寫入容量規劃。CockroachDB 改成 Raft per range、每個 node 都能服務寫入、容量隨節點線性擴。</li>
<li><strong>Migration 工具自製是先決條件、不是 nice-to-have</strong>：DoorDash 沒「一次性遷整套」、而是先寫工具把 table 從主 cluster 拆到獨立 Aurora cluster（紓壓）、再寫第二套工具把 Aurora → CockroachDB（換引擎）。兩階段都要 <em>lossless</em> + <em>可回退</em>。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的「先建工具、再遷資料」原則。</li>
<li><strong>Cluster 數量增加、alert volume 卻下降</strong>：直覺反過來、cluster 多 = 維運面變大、應該更多 alert。但每個 CockroachDB cluster 內建 Raft 自動容錯、單節點 fail 不會 page on-call、Aurora 時代的「primary failover alert」消失。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的「告警 surface 設計」與 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06.x reliability</a> 的 graceful degradation。</li>
</ol>
<p>需要警惕：1.636 M QPS 是 <em>主 cluster 峰值</em>、不是「DoorDash 全部寫入 QPS」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster」。讀案例時不要把這個數字當成「CockroachDB 撐 1.6 M QPS」的證據、它是 <em>Aurora 在那個時間點撞牆的痛點</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>single-primary 撞牆前、先評估 multi-primary 選項</strong>：Aurora / RDS Postgres 是 single-primary 為主、寫入量持續成長最終會撞天花板。轉折點不是 IOPS、是 <em>primary CPU + WAL flush rate</em>。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的瓶頸辨識。</li>
<li><strong>遷 OLTP 引擎要走「兩階段紓壓」</strong>：先在原引擎內把 hot table 拆出（降低主 cluster 壓力、爭取時間）、再規劃換引擎（架構級改造）。直接「一次性換引擎」風險過高。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
<li><strong>PostgreSQL wire protocol 相容性是降低遷移成本的關鍵</strong>：DoorDash 保留 PostgreSQL driver / ORM、應用層改動小。CockroachDB 不是 PostgreSQL fork、是 <em>protocol-level 相容</em>、實際 SQL 行為（serializable default、retry semantics、partial index）仍要驗證。對應 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 PostgreSQL 相容性 audit 段。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS Aurora DSQL（2024）解同類「multi-primary 寫入」問題、但 AWS-only</li>
<li>Spanner（GCP）同類設計、GCP-only</li>
<li>TiDB（MySQL wire）解同類問題、亞洲生態深</li>
<li>自管 PostgreSQL + Citus（sharded extension）走 application 層 sharding、operation burden 較高</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解 single-primary 寫入天花板訊號 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想規劃 PostgreSQL → CockroachDB migration → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 OLTP 規模化案例 → <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora</a>（按業務切 cluster）/ <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a>（DB 種類整合）</li>
<li>想對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix CockroachDB fleet</a> / <a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a></li>
<li>想理解全球一致性 OLTP 選型 → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a></li>
<li>想拆 CockroachDB transaction retry 與 contention 模式 → <a href="/blog/backend/01-database/vendors/cockroachdb/transaction-retry-pattern/" data-link-title="CockroachDB Transaction Retry Pattern：serializable default 與 application contract 重塑" data-link-desc="CockroachDB default SERIALIZABLE、application 必須包 retry loop 處理 40001 serialization_failure。本文走 PG → CockroachDB application contract 重塑視角、SAVEPOINT cockroach_restart 語法、5 種失敗模式（retry storm / 非冪等 / cross-statement state / hot row / long-running transaction）。**整篇是跨 case 合成 frame**：DoorDash case 沒揭露 retry pattern、只揭露 PG wire protocol 相容 &#43; SQL 行為仍要 audit、本章 retry contract 重塑屬通用工程議題從 Cockroach Labs 官方 docs 合成">CockroachDB transaction retry pattern</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB 的選型 → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB</a></li>
<li><a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">How DoorDash Migrated from Aurora Postgres to CockroachDB（The New Stack）</a></li>
<li><a href="https://careersatdoordash.com/blog/how-we-scaled-new-verticals-fulfillment-backend-with-cockroachdb/">How We Scaled New Verticals Fulfillment Backend with CockroachDB（DoorDash Engineering Blog）</a></li>
<li><a href="https://www.infoq.com/news/2024/02/doordash-config-cockroachdb/">DoorDash Uses CockroachDB to Create Config Management Platform for Microservices（InfoQ）</a></li>
</ul>
]]></content:encoded></item><item><title>3.C39 Choria：NATS 管 50 萬 server fleet</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/</guid><description>&lt;p>這個案例的核心責任是說明 fire-and-forget RPC + scatter-gather pattern 是 NATS Core 的典型場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Choria 是 Puppet MCollective 的現代化替代品、目標管理數萬到數十萬節點的 fleet 同時下指令。評估過多個 broker、選 NATS 因為「單 binary、無 Zookeeper 依賴、Ruby client 品質好」、實測「單 server 300MB RAM 管 2000+ 機器」、4GB 節點可達 50 萬 server。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>MCollective 的 fire-and-forget RPC 語意正好對應 NATS Core 的 stateless best-effort + request-reply pattern、用 wildcard subject + queue group 做 parallel scatter-gather RPC。揭露 server orchestration 場景不需要 persistence、Core NATS 已足夠。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Request/Reply pattern / Queue groups / Cluster + Supercluster + Leaf node（Choria Federation Broker = 跨地理 federation）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/nats-for-the-marionette-collective/">NATS for the Marionette Collective (Choria)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://choria.io/docs/concepts/">Choria Architecture Docs&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 fire-and-forget RPC + scatter-gather pattern 是 NATS Core 的典型場景。</p>
<h2 id="觀察">觀察</h2>
<p>Choria 是 Puppet MCollective 的現代化替代品、目標管理數萬到數十萬節點的 fleet 同時下指令。評估過多個 broker、選 NATS 因為「單 binary、無 Zookeeper 依賴、Ruby client 品質好」、實測「單 server 300MB RAM 管 2000+ 機器」、4GB 節點可達 50 萬 server。</p>
<h2 id="判讀">判讀</h2>
<p>MCollective 的 fire-and-forget RPC 語意正好對應 NATS Core 的 stateless best-effort + request-reply pattern、用 wildcard subject + queue group 做 parallel scatter-gather RPC。揭露 server orchestration 場景不需要 persistence、Core NATS 已足夠。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Request/Reply pattern / Queue groups / Cluster + Supercluster + Leaf node（Choria Federation Broker = 跨地理 federation）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/nats-for-the-marionette-collective/">NATS for the Marionette Collective (Choria)</a></li>
<li><a href="https://choria.io/docs/concepts/">Choria Architecture Docs</a></li>
</ul>
]]></content:encoded></item><item><title>9.C40 Netflix：380+ CockroachDB cluster 的 multi-active 拓樸艦隊</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/</guid><description>&lt;p>這個案例的核心責任是說明「Cassandra 撐不住 transactional 一致性」如何用 distributed SQL 補位。Netflix &lt;em>用 CockroachDB 補 Cassandra 缺的那塊&lt;/em>、全面替換從來不是策略：需要 rich transaction + global secondary index + multi-active 寫入的場景。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation&lt;/a> 對照 — Aurora 整合的是 OLTP single-region workload、CockroachDB 解的是「跨 region 強一致 + 跨 cluster 高彈性」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Netflix CockroachDB 艦隊的關鍵數字（引自 &lt;a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters&lt;/a> / &lt;a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>總 cluster 數&lt;/td>
 &lt;td>380+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production cluster&lt;/td>
 &lt;td>160+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-region cluster&lt;/td>
 &lt;td>60+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>最大單區 cluster&lt;/td>
 &lt;td>60 nodes / 26.5 TB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Gaming 平台 cluster&lt;/td>
 &lt;td>48 nodes、跨 4 個 region&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>首個 prod cluster&lt;/td>
 &lt;td>2020 上線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production cluster&lt;/td>
 &lt;td>2022 已達 100、近年擴至 160+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署拓樸常態&lt;/td>
 &lt;td>多數 single-region、3 個 AZ&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：CockroachDB self-managed（Netflix Database Platform Team 自運維）、跨 AWS region、與 Cassandra / EVCache / RDS 並存（polyglot persistence）。&lt;/p>
&lt;p>關鍵 workload：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Studio Cloud Drive&lt;/strong>：影視製作資產的 file-system 風格服務、需要強一致 metadata + 全球可寫&lt;/li>
&lt;li>&lt;strong>Open Connect 控制平面&lt;/strong>：Netflix 自有 CDN、控制全球網路設備、需要跨 region 一致 control state&lt;/li>
&lt;li>&lt;strong>Spinnaker（持續交付平台）&lt;/strong>：deployment workflow state 需要 transactional 一致&lt;/li>
&lt;li>&lt;strong>Maestro（ML / 資料 workflow orchestration）&lt;/strong>：scheduling 與 state machine 不容許 eventual consistency&lt;/li>
&lt;li>&lt;strong>Gaming control plane&lt;/strong>：metadata 跨 4 region、region failure 不能 downtime&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Netflix CockroachDB 艦隊揭露三個「補 Cassandra 缺口」的 OLTP 工程選擇。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cassandra 不是 transactional 引擎、補位需求是工程現實&lt;/strong>：Netflix 2014 全面採用 Cassandra 解 global replication、但 &lt;em>lightweight transaction&lt;/em> 跟 unreliable secondary index 在 studio / control plane 等場景出問題。2019 評估後選 CockroachDB 是因為它同時滿足 multi-active topology、global consistent secondary index、global transaction、open source、SQL — 五個條件 Cassandra 在 transactional 場景下湊不齊。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 polyglot persistence 與 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>380+ cluster ≠ 「一個巨型 DB」&lt;/strong>：Netflix 是 &lt;em>artery of small DBs&lt;/em> 模型 — 每個微服務 / 應用配自己的 cluster、cluster sizing 從幾個 node 到 60 nodes 不等。容量規劃變成「每個 cluster 各自規劃」、不是「全公司一個容量曲線」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a> 的「微服務私有 store」哲學。&lt;/li>
&lt;li>&lt;strong>Multi-region 是「region failure 0 downtime」、不是「更快」&lt;/strong>：Netflix 60+ multi-region cluster 主要動機是 region-level survival、不是降 latency（跨 region quorum 反而會增 latency）。Gaming cluster 48-node 跨 4 region 就是為了「region failover 不停服」、不是讓玩家延遲變低。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency vs availability 取捨。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「Cassandra 撐不住 transactional 一致性」如何用 distributed SQL 補位。Netflix <em>用 CockroachDB 補 Cassandra 缺的那塊</em>、全面替換從來不是策略：需要 rich transaction + global secondary index + multi-active 寫入的場景。跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 對照 — Aurora 整合的是 OLTP single-region workload、CockroachDB 解的是「跨 region 強一致 + 跨 cluster 高彈性」。</p>
<h2 id="觀察">觀察</h2>
<p>Netflix CockroachDB 艦隊的關鍵數字（引自 <a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters</a> / <a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>總 cluster 數</td>
          <td>380+</td>
      </tr>
      <tr>
          <td>Production cluster</td>
          <td>160+</td>
      </tr>
      <tr>
          <td>Multi-region cluster</td>
          <td>60+</td>
      </tr>
      <tr>
          <td>最大單區 cluster</td>
          <td>60 nodes / 26.5 TB</td>
      </tr>
      <tr>
          <td>Gaming 平台 cluster</td>
          <td>48 nodes、跨 4 個 region</td>
      </tr>
      <tr>
          <td>首個 prod cluster</td>
          <td>2020 上線</td>
      </tr>
      <tr>
          <td>Production cluster</td>
          <td>2022 已達 100、近年擴至 160+</td>
      </tr>
      <tr>
          <td>部署拓樸常態</td>
          <td>多數 single-region、3 個 AZ</td>
      </tr>
  </tbody>
</table>
<p>服務組合：CockroachDB self-managed（Netflix Database Platform Team 自運維）、跨 AWS region、與 Cassandra / EVCache / RDS 並存（polyglot persistence）。</p>
<p>關鍵 workload：</p>
<ul>
<li><strong>Studio Cloud Drive</strong>：影視製作資產的 file-system 風格服務、需要強一致 metadata + 全球可寫</li>
<li><strong>Open Connect 控制平面</strong>：Netflix 自有 CDN、控制全球網路設備、需要跨 region 一致 control state</li>
<li><strong>Spinnaker（持續交付平台）</strong>：deployment workflow state 需要 transactional 一致</li>
<li><strong>Maestro（ML / 資料 workflow orchestration）</strong>：scheduling 與 state machine 不容許 eventual consistency</li>
<li><strong>Gaming control plane</strong>：metadata 跨 4 region、region failure 不能 downtime</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Netflix CockroachDB 艦隊揭露三個「補 Cassandra 缺口」的 OLTP 工程選擇。</p>
<ol>
<li><strong>Cassandra 不是 transactional 引擎、補位需求是工程現實</strong>：Netflix 2014 全面採用 Cassandra 解 global replication、但 <em>lightweight transaction</em> 跟 unreliable secondary index 在 studio / control plane 等場景出問題。2019 評估後選 CockroachDB 是因為它同時滿足 multi-active topology、global consistent secondary index、global transaction、open source、SQL — 五個條件 Cassandra 在 transactional 場景下湊不齊。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence 與 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a>。</li>
<li><strong>380+ cluster ≠ 「一個巨型 DB」</strong>：Netflix 是 <em>artery of small DBs</em> 模型 — 每個微服務 / 應用配自己的 cluster、cluster sizing 從幾個 node 到 60 nodes 不等。容量規劃變成「每個 cluster 各自規劃」、不是「全公司一個容量曲線」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的「微服務私有 store」哲學。</li>
<li><strong>Multi-region 是「region failure 0 downtime」、不是「更快」</strong>：Netflix 60+ multi-region cluster 主要動機是 region-level survival、不是降 latency（跨 region quorum 反而會增 latency）。Gaming cluster 48-node 跨 4 region 就是為了「region failover 不停服」、不是讓玩家延遲變低。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency vs availability 取捨。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>case study 沒揭露單一 cluster QPS / latency 具體數字、只揭露 <em>艦隊規模</em> 跟 <em>最大 cluster 容量</em>。讀案例時不要把「380 cluster」直接換算成「Netflix CockroachDB QPS 上限」。</li>
<li>Netflix 是 <em>self-managed</em>、不是 Cockroach Cloud — 需要專屬 Database Platform Team 養 380+ cluster。沒這量級團隊的組織直接 self-host 380 cluster 是 ops 自殺、Cockroach Cloud 才是合理路徑。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>不要試圖一個 DB 撐全部</strong>：Netflix 同時用 Cassandra（高吞吐 eventual）、CockroachDB（transactional + global）、Aurora（單區 ACID）、EVCache（cache）。每種 DB 對應不同 workload 類型、不混用。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence。</li>
<li><strong>每個 cluster 對應一個 application boundary</strong>：避免 multi-tenant 大 cluster、改用「per-app cluster」— 容量規劃顆粒對齊 application、爆掉時 blast radius 限縮在單一 app。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的 blast radius 設計。</li>
<li><strong>Multi-region 用於 survival、不是 latency 優化</strong>：跨 region quorum 物理上必然增 latency。把 multi-region 動機釐清成 <em>region failure 容忍</em>、不要混淆「跨 region = 更快」。對應 <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> 的 survival goal vs latency budget 取捨。</li>
<li><strong>Self-managed 規模化需要專屬平台團隊</strong>：Netflix 有 Database Platform Team 養 380+ cluster — 包含 backup、upgrade、incident response、capacity review。沒這量級團隊就走 managed service。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本權衡。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>Spanner（GCP）解同類「global transaction + secondary index」、GCP-only</li>
<li>DynamoDB Global Tables 走 eventual consistency、不是 Netflix 想要的 strong consistency</li>
<li>Yugabyte / TiDB 是 distributed SQL 對等候選、生態深度與 PostgreSQL wire 相容度有差</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解 polyglot persistence 選型 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a></li>
<li>想規劃 multi-region survival goal → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/" data-link-title="9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入" data-link-desc="DoorDash 從 Aurora Postgres 遷到 CockroachDB、解 1.6 M QPS 單主寫入瓶頸、外送平台爆量壓力下重做 OLTP 拓樸">9.C39 DoorDash</a> / <a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想理解 transaction vs eventual consistency 邊界 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想深入 CockroachDB survival goal 與 region failure 取捨 → <a href="/blog/backend/01-database/vendors/cockroachdb/survival-goals/" data-link-title="CockroachDB Survival Goals：zone 級 vs region 級配置與業務 SLO 倒推流程" data-link-desc="CockroachDB 用 SURVIVE ZONE FAILURE / SURVIVE REGION FAILURE 兩種 survival goal 宣告式控制 Raft replica 分佈、決定 RTO / RPO。本文走 Hard Rock Digital bet placement RPO=0 倒推流程、Netflix Gaming 48-node 跨 4 region 「為求 survival 而非 latency」的反直覺判讀、配置語法、寫入 latency 暴漲跟 cost 暴漲兩條失敗模式、合規邊界對比">CockroachDB survival goals</a></li>
<li>想規劃跨 region schema 與資料本地化 → <a href="/blog/backend/01-database/vendors/cockroachdb/locality-aware-schema/" data-link-title="CockroachDB Locality-Aware Schema：跨州合規 &#43; 邏輯一個 cluster 的 region placement 策略" data-link-desc="Hard Rock Digital 跨 8 州 sportsbook、用 AWS Outposts &#43; region placement 把運算釘在州內、邏輯上仍是一個 CockroachDB cluster。本文走 REGIONAL BY ROW / REGIONAL BY TABLE / GLOBAL 三種 locality、Hard Rock 拓樸創新對比 Standard Chartered Aurora 7 cluster fleet、AWS Outposts 是合規工具不是 latency 工具的反直覺判讀">CockroachDB locality-aware schema</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://assets.ctfassets.net/00voh0j35590/7qBPsA0FKKTuAK4JhK27uu/1b30b2015f32878874bd0873a2a54361/CockroachLabs-NETFLIX-Case-Study.pdf">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters（PDF）</a></li>
<li><a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters（cockroachlabs.com Netflix customer page）</a></li>
<li><a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix: From Cassandra to CockroachDB</a></li>
<li><a href="https://www.cockroachlabs.com/blog/netflix-dbaas-roachfest24-recap/">A Netflix RoachFest24 Original: The Case for Multi-Region Clusters</a></li>
<li><a href="https://www.cockroachlabs.com/blog/persistence-as-a-service-at-netflix/">How Netflix engineers choose their tech stack</a></li>
</ul>
]]></content:encoded></item><item><title>3.C40 Resgate：WebSocket-to-NATS realtime API gateway</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/</guid><description>&lt;p>這個案例的核心責任是說明「subject hierarchy 即 access control 邊界」的設計範例。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Resgate 把 NATS subject 暴露成 REST + WebSocket、客戶端跨多 Resgate 實例自動同步狀態、事件延遲 &amp;lt; 1ms。需要同時支援 pub-sub 跟 request-reply、選 NATS 因為「performance、simplicity、兩種模式都原生支援」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>subject 設計遵循 &lt;code>get.{service}.{resource}&lt;/code> / &lt;code>event.{service}.{resource}.{event-type}&lt;/code> 的命名規約、是「subject 階層當 schema」的典型範例。揭露 subject 命名是 NATS 的 API contract 起點、不是隨意命名。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Request/Reply pattern / Subject-based ACL + 多租戶（subject hierarchy 即 access control 邊界）/ Core NATS vs JetStream（純 Core）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &amp;#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26 GoCardless Hutch routing key&lt;/a>（命名規約對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://resgate.io/blog/introducing-resgate/">Introducing Resgate&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「subject hierarchy 即 access control 邊界」的設計範例。</p>
<h2 id="觀察">觀察</h2>
<p>Resgate 把 NATS subject 暴露成 REST + WebSocket、客戶端跨多 Resgate 實例自動同步狀態、事件延遲 &lt; 1ms。需要同時支援 pub-sub 跟 request-reply、選 NATS 因為「performance、simplicity、兩種模式都原生支援」。</p>
<h2 id="判讀">判讀</h2>
<p>subject 設計遵循 <code>get.{service}.{resource}</code> / <code>event.{service}.{resource}.{event-type}</code> 的命名規約、是「subject 階層當 schema」的典型範例。揭露 subject 命名是 NATS 的 API contract 起點、不是隨意命名。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Request/Reply pattern / Subject-based ACL + 多租戶（subject hierarchy 即 access control 邊界）/ Core NATS vs JetStream（純 Core）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26 GoCardless Hutch routing key</a>（命名規約對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://resgate.io/blog/introducing-resgate/">Introducing Resgate</a></li>
</ul>
]]></content:encoded></item><item><title>9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 + 跨州單一邏輯 DB</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/</guid><description>&lt;p>這個案例的核心責任是說明「合規強制資料留地理邊界 + 想要單一邏輯 DB」如何用 distributed SQL + 邊緣硬體解。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered&lt;/a> 對比 — Standard Chartered 走「Aurora 多 region、each region 一個 cluster」、Hard Rock Digital 走「跨 AWS Outposts + AWS region 一個邏輯 cluster」。兩條都解受監管金融類業務、結構差異反映法規顆粒不同：銀行是國家層級、美國運動博彩是 &lt;em>州&lt;/em> 層級。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Hard Rock Digital sportsbook 部署的關鍵數字（引自 &lt;a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital customer page&lt;/a> / &lt;a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app&lt;/a>）：&lt;/p>
&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>8（AZ / IN / TN / FL / OH / IL / NJ / VA）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高峰節點數&lt;/td>
 &lt;td>~100 nodes、each 32 vCPU&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>淡季節點數&lt;/td>
 &lt;td>scales down ~33 nodes（約 1/3）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施組合&lt;/td>
 &lt;td>AWS Regions + AWS Local Zones + AWS Outposts（按州合規要求布局）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料庫拓樸&lt;/td>
 &lt;td>跨所有 region 一個 logical database&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Survival goal&lt;/td>
 &lt;td>單一 Outpost 或 AWS AZ 失敗不丟資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>顯著測試失敗事件&lt;/td>
 &lt;td>node crash / EC2 instance fail / single state loss — 對使用者無感&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重大事件流量&lt;/td>
 &lt;td>Super Bowl / World Cup 等高峰、無效能退化紀錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Engineering 團隊&lt;/td>
 &lt;td>tech team ~50 人；若用 PostgreSQL 估計需多加 10-20 工程師&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：CockroachDB self-managed、AWS US-East-1（共用 control plane）、AWS Outposts（部分州合規要求設備位於州內）、AWS Local Zones（特定都會區延遲補強）。&lt;/p>
&lt;p>關鍵 workload：bet placement、bet settlement、account management、cache loading、sports metadata import。&lt;/p>
&lt;p>關鍵負載形狀：sports betting 是 &lt;em>event-driven peak&lt;/em> — Super Bowl / World Cup 等賽事是已知時間點、流量在開賽前 30-60 分鐘飆升、賽中持續高水位、賽後 settlement 集中爆發。「100 → 33 → 100」的 scale up / down 反映賽季 vs 淡季的容量需求差。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Hard Rock Digital 的工程選擇揭露三個受監管 OLTP 的設計重點。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「合規強制資料留地理邊界 + 想要單一邏輯 DB」如何用 distributed SQL + 邊緣硬體解。跟 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> 對比 — Standard Chartered 走「Aurora 多 region、each region 一個 cluster」、Hard Rock Digital 走「跨 AWS Outposts + AWS region 一個邏輯 cluster」。兩條都解受監管金融類業務、結構差異反映法規顆粒不同：銀行是國家層級、美國運動博彩是 <em>州</em> 層級。</p>
<h2 id="觀察">觀察</h2>
<p>Hard Rock Digital sportsbook 部署的關鍵數字（引自 <a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital customer page</a> / <a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>營運州數</td>
          <td>8（AZ / IN / TN / FL / OH / IL / NJ / VA）</td>
      </tr>
      <tr>
          <td>高峰節點數</td>
          <td>~100 nodes、each 32 vCPU</td>
      </tr>
      <tr>
          <td>淡季節點數</td>
          <td>scales down ~33 nodes（約 1/3）</td>
      </tr>
      <tr>
          <td>基礎設施組合</td>
          <td>AWS Regions + AWS Local Zones + AWS Outposts（按州合規要求布局）</td>
      </tr>
      <tr>
          <td>資料庫拓樸</td>
          <td>跨所有 region 一個 logical database</td>
      </tr>
      <tr>
          <td>Survival goal</td>
          <td>單一 Outpost 或 AWS AZ 失敗不丟資料</td>
      </tr>
      <tr>
          <td>顯著測試失敗事件</td>
          <td>node crash / EC2 instance fail / single state loss — 對使用者無感</td>
      </tr>
      <tr>
          <td>重大事件流量</td>
          <td>Super Bowl / World Cup 等高峰、無效能退化紀錄</td>
      </tr>
      <tr>
          <td>Engineering 團隊</td>
          <td>tech team ~50 人；若用 PostgreSQL 估計需多加 10-20 工程師</td>
      </tr>
  </tbody>
</table>
<p>服務組合：CockroachDB self-managed、AWS US-East-1（共用 control plane）、AWS Outposts（部分州合規要求設備位於州內）、AWS Local Zones（特定都會區延遲補強）。</p>
<p>關鍵 workload：bet placement、bet settlement、account management、cache loading、sports metadata import。</p>
<p>關鍵負載形狀：sports betting 是 <em>event-driven peak</em> — Super Bowl / World Cup 等賽事是已知時間點、流量在開賽前 30-60 分鐘飆升、賽中持續高水位、賽後 settlement 集中爆發。「100 → 33 → 100」的 scale up / down 反映賽季 vs 淡季的容量需求差。</p>
<h2 id="判讀">判讀</h2>
<p>Hard Rock Digital 的工程選擇揭露三個受監管 OLTP 的設計重點。</p>
<ol>
<li><strong>法規顆粒決定基礎設施拓樸、不是反過來</strong>：美國 Wire Act 要求 <em>betting data 必須在下注州內處理</em>、所以每個營運州都要有州內運算資源。傳統路徑是「每州一個獨立 silo」— 但 silo 之間的玩家統一帳戶、跨州 reporting、欺詐偵測會撞牆。Hard Rock Digital 用 AWS Outposts 把運算放進州內、但邏輯上仍是 <em>一個</em> CockroachDB cluster — region placement 配置決定哪些 range 釘在哪個 Outpost、合規與單一邏輯 DB 同時成立。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的合規 boundary 設計與 <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> 的 region placement。</li>
<li><strong>Survival goal 「Outpost 或 AZ 失敗不丟」對應業務 SLO</strong>：sports betting 中 <em>bet placement</em> 不能 lose — 玩家下注後系統 crash 沒紀錄、對博彩牌照是合規事故。CockroachDB Raft 3-replica + 跨 AZ 配置讓 Outpost 失敗時其他 replica 還在、自動 failover。對應 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06 reliability</a> 的 RPO=0 設計與 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 Survival Goals。</li>
<li><strong>Scale up / down 是賽季常態、不是異常事件</strong>：100 → 33 → 100 的擺盪在 sportsbook 業務是 <em>年度循環</em> — NFL 季結束 / NBA 季初切換、流量結構性下降。CockroachDB 加減節點靠 range rebalance、不停服。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 seasonality 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的 event-driven scaling。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>case study 沒揭露 QPS、p99 latency 具體數字。100 node × 32 vCPU 是硬體規模、不是 throughput。讀案例時要區分 <em>容量 sizing</em>（節點數）跟 <em>workload throughput</em>（每秒處理量）。</li>
<li>「省了 10-20 工程師」是 <em>估計差距</em>、不是已 hire 後解雇。對應的是「沒選 PostgreSQL 所以沒招那麼多 DBA」、是機會成本不是節省支出。</li>
<li>Wire Act 是 <em>美國聯邦法</em>、各州還有獨立法規（NJ DGE、NV NGC 等）。Hard Rock Digital 模型適合 <em>跨州</em> 合規、不是 <em>跨國</em> — 跨國牌照差異更大、不能直接套。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>合規 boundary 用 region placement 表達、不是 cluster fragmentation</strong>：當法規要求資料留某地理邊界、優先看 distributed SQL 的 region placement / pin-to-region 能力、不要直接開獨立 cluster。獨立 cluster 解了合規但破壞了業務邏輯（跨州統一帳戶、欺詐偵測、reporting）。對應 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 multi-region table 與 <a href="/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Spanner vendor</a> 的 placement。</li>
<li><strong>邊緣硬體（AWS Outposts / Local Zones）是合規工具、不是 latency 工具</strong>：Outposts 主要為「資料留某地理邊界」而存在、latency 改善是副作用。決策時先看合規驅動力、latency 改善列為 bonus。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 hybrid cloud 設計。</li>
<li><strong>賽季型擴縮容寫進 baseline 容量模型</strong>：Hard Rock Digital 100 ↔ 33 的擺盪不是「臨時 scale up」、是計畫內年度循環。容量規劃要直接把 NFL / NBA / 國際賽事曆塞進預測模型、不要當 surprise。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 與 <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech 體育博彩 AI 預測</a>。</li>
<li><strong>distributed SQL 的 ops 槓桿：team 小、cluster 大</strong>：Hard Rock Digital 50 人 tech team 養全部運維、估省了 10-20 個 DBA。distributed SQL 把「DBA 養單區、跨區 sync 養運維」的工作量壓進 <em>系統內建</em> 的 Raft / placement、人月支出降。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>Spanner（GCP）也支援 region placement、但 GCP-only、無 Outposts 等效</li>
<li>Aurora DSQL（AWS 2024）支援跨 region 強一致、但 Outpost 部署現階段未完整覆蓋</li>
<li>自管 PostgreSQL + application 層 sharding：理論可行、operation burden 跟人力需求大幅上升、Hard Rock Digital 評估後選 CockroachDB 的主因之一</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他受監管金融 / 博彩 OLTP → <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a>（銀行國家層級）/ <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a>（fantasy sports）</li>
<li>對照 event-driven peak 設計 → <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> / <a href="/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &#43; Wavelength &#43; Outposts 處理 20&#43; 州的雙重峰值">9.C28 FanDuel</a></li>
<li>想規劃 multi-region OLTP survival goal → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/" data-link-title="9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入" data-link-desc="DoorDash 從 Aurora Postgres 遷到 CockroachDB、解 1.6 M QPS 單主寫入瓶頸、外送平台爆量壓力下重做 OLTP 拓樸">9.C39 DoorDash</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想理解合規驅動的拓樸設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想拆 CockroachDB survival goal 與合規拓樸對齊 → <a href="/blog/backend/01-database/vendors/cockroachdb/survival-goals/" data-link-title="CockroachDB Survival Goals：zone 級 vs region 級配置與業務 SLO 倒推流程" data-link-desc="CockroachDB 用 SURVIVE ZONE FAILURE / SURVIVE REGION FAILURE 兩種 survival goal 宣告式控制 Raft replica 分佈、決定 RTO / RPO。本文走 Hard Rock Digital bet placement RPO=0 倒推流程、Netflix Gaming 48-node 跨 4 region 「為求 survival 而非 latency」的反直覺判讀、配置語法、寫入 latency 暴漲跟 cost 暴漲兩條失敗模式、合規邊界對比">CockroachDB survival goals</a></li>
<li>想做 region pinning 與在地化 schema → <a href="/blog/backend/01-database/vendors/cockroachdb/locality-aware-schema/" data-link-title="CockroachDB Locality-Aware Schema：跨州合規 &#43; 邏輯一個 cluster 的 region placement 策略" data-link-desc="Hard Rock Digital 跨 8 州 sportsbook、用 AWS Outposts &#43; region placement 把運算釘在州內、邏輯上仍是一個 CockroachDB cluster。本文走 REGIONAL BY ROW / REGIONAL BY TABLE / GLOBAL 三種 locality、Hard Rock 拓樸創新對比 Standard Chartered Aurora 7 cluster fleet、AWS Outposts 是合規工具不是 latency 工具的反直覺判讀">CockroachDB locality-aware schema</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB 給博彩 OLTP → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital: scaling a performant sports betting platform（cockroachlabs.com customer page）</a></li>
<li><a href="https://downloads.ctfassets.net/00voh0j35590/7dKNWhsW4RjpUlFgzHB8qw/752a22c833c879bca503bbffb2b584c7/CockroachLabs-Hard-Rock-Digital-Case-Study-v2.pdf">Hard Rock, anytime, anywhere: scaling a performant sports betting platform（PDF case study）</a></li>
<li><a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app</a></li>
<li><a href="https://www.cockroachlabs.com/blog/real-money-gaming-reference-architecture/">Building a sports betting application to handle &lsquo;Big Game&rsquo; traffic</a></li>
<li><a href="https://www.cockroachlabs.com/solutions/verticals/gambling/">CockroachDB for Gambling solutions page</a></li>
</ul>
]]></content:encoded></item><item><title>3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/</guid><description>&lt;p>這個案例的核心責任是說明 OT/IT 整合場景的多工廠 leaf node 拓樸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>i-flow 是工業數據整合平台、每日 4 億筆 data operation、提供 200+ OT/IT 系統 connector、客戶含 Fortune 500 工廠（Bosch、Sto、Lenze）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 NATS 當 OT/IT 跨層整合 bus、邊緣端負責 connect / harmonize / publish。揭露多工廠場景該用 leaf node hub-and-spoke、不該每工廠自管 cluster。&lt;strong>注意&lt;/strong>：此案例技術細節較淺、引用時要補其他案例的具體 stream / consumer 設計。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node（多工廠 leaf node 連 central）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &amp;#43; JetStream &amp;#43; KV &amp;#43; Object Store。">3.C37 MachineMetrics&lt;/a>（技術細節更深的對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/i-flow-case-study/">i-flow Case Study&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 OT/IT 整合場景的多工廠 leaf node 拓樸。</p>
<h2 id="觀察">觀察</h2>
<p>i-flow 是工業數據整合平台、每日 4 億筆 data operation、提供 200+ OT/IT 系統 connector、客戶含 Fortune 500 工廠（Bosch、Sto、Lenze）。</p>
<h2 id="判讀">判讀</h2>
<p>用 NATS 當 OT/IT 跨層整合 bus、邊緣端負責 connect / harmonize / publish。揭露多工廠場景該用 leaf node hub-and-spoke、不該每工廠自管 cluster。<strong>注意</strong>：此案例技術細節較淺、引用時要補其他案例的具體 stream / consumer 設計。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node（多工廠 leaf node 連 central）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a>（技術細節更深的對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/i-flow-case-study/">i-flow Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>Azure AD：2021 身分控制面中斷事件</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/</guid><description>&lt;p>這起案例的核心責任是處理身份控制面故障對下游產品的連鎖影響。身份系統事故通常擴散快、影響廣，分級與通訊需要提前對齊。&lt;/p>
&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>auth failure surge&lt;/td>
 &lt;td>影響是否跨產品擴散&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>token issuance lag&lt;/td>
 &lt;td>控制面是否壅塞&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp;amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>dependency blast radius&lt;/td>
 &lt;td>下游受影響範圍&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/vendor-dependency-incident/" data-link-title="8.15 Vendor / 第三方依賴事故處理" data-link-desc="依賴方掛掉、自己無 control 時的決策模型">8.15&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「身份控制面」對下游產品鏈的連鎖影響。主要風險是事件分級只看單一產品，忽略共用身份依賴的擴散速度。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先做影響分層，再同步外部通訊與回復節奏，並將判讀欄位回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是處理身份控制面故障對下游產品的連鎖影響。身份系統事故通常擴散快、影響廣，分級與通訊需要提前對齊。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>auth failure surge</td>
          <td>影響是否跨產品擴散</td>
          <td><a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1</a></td>
      </tr>
      <tr>
          <td>token issuance lag</td>
          <td>控制面是否壅塞</td>
          <td><a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18</a></td>
      </tr>
      <tr>
          <td>dependency blast radius</td>
          <td>下游受影響範圍</td>
          <td><a href="/blog/backend/08-incident-response/vendor-dependency-incident/" data-link-title="8.15 Vendor / 第三方依賴事故處理" data-link-desc="依賴方掛掉、自己無 control 時的決策模型">8.15</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「身份控制面」對下游產品鏈的連鎖影響。主要風險是事件分級只看單一產品，忽略共用身份依賴的擴散速度。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先做影響分層，再同步外部通訊與回復節奏，並將判讀欄位回寫 <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a> 與 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a>。</p>
]]></content:encoded></item><item><title>Meta：Region Failover 與可靠性邊界</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/</guid><description>&lt;p>Meta 案例的核心責任是處理跨區故障時的邊界與回復順序。大規模平台的關鍵風險在跨區相依引發的連鎖退化，單點失效反而是較好處理的情況。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>當核心網路或控制面異常跨越區域邊界，若沒有預先定義故障域與回復順序，恢復動作本身會變成新的放大器。&lt;/p>
&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>Region fault domain&lt;/td>
 &lt;td>影響面最多到哪裡&lt;/td>
 &lt;td>故障邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ordered failover&lt;/td>
 &lt;td>先恢復哪條路徑&lt;/td>
 &lt;td>回復順序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dependency isolation&lt;/td>
 &lt;td>共享相依如何降風險&lt;/td>
 &lt;td>局部化策略&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>cross-region error spread&lt;/td>
 &lt;td>擴散是否越界&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>failover completion lag&lt;/td>
 &lt;td>回復批次是否收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>shared dependency saturation&lt;/td>
 &lt;td>共享依賴是否成瓶頸&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先定義 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a> 的演練範圍，再回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a> 的決策欄位。&lt;/p></description><content:encoded><![CDATA[<p>Meta 案例的核心責任是處理跨區故障時的邊界與回復順序。大規模平台的關鍵風險在跨區相依引發的連鎖退化，單點失效反而是較好處理的情況。</p>
<h2 id="問題場景">問題場景</h2>
<p>當核心網路或控制面異常跨越區域邊界，若沒有預先定義故障域與回復順序，恢復動作本身會變成新的放大器。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Region fault domain</td>
          <td>影響面最多到哪裡</td>
          <td>故障邊界</td>
      </tr>
      <tr>
          <td>Ordered failover</td>
          <td>先恢復哪條路徑</td>
          <td>回復順序</td>
      </tr>
      <tr>
          <td>Dependency isolation</td>
          <td>共享相依如何降風險</td>
          <td>局部化策略</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>cross-region error spread</td>
          <td>擴散是否越界</td>
          <td><a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a></td>
      </tr>
      <tr>
          <td>failover completion lag</td>
          <td>回復批次是否收斂</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>shared dependency saturation</td>
          <td>共享依賴是否成瓶頸</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>先定義 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a> 的演練範圍，再回寫 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a> 的決策欄位。</p>
]]></content:encoded></item><item><title>Stripe：Idempotency 與零停機遷移的交易安全設計</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/</guid><description>&lt;p>Stripe 案例的核心責任是確保交易語義在重試與變更中保持一致。支付系統的失效成本不只來自停機，還來自錯誤結果；因此可靠性設計要同時守住可用性與正確性。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>交易系統最常見的高風險組合是：客戶端重試、網路抖動、後端部署或資料遷移同時發生。若系統只處理單一失效，結果往往是可用但不一致，或者一致但無法持續交付。&lt;/p>
&lt;p>idempotency key 與 zero-downtime migration 的組合，目標是讓這些變更在同一套邊界下可判讀。&lt;/p>
&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>Idempotency key&lt;/td>
 &lt;td>同一交易重送如何得到同一結果&lt;/td>
 &lt;td>重試安全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Expand/contract migration&lt;/td>
 &lt;td>資料變更如何與新舊版本共存&lt;/td>
 &lt;td>漸進遷移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Canary + rollback gate&lt;/td>
 &lt;td>發版異常如何快速收斂&lt;/td>
 &lt;td>可回復交付&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Transaction-path observability&lt;/td>
 &lt;td>交易路徑是否可追溯&lt;/td>
 &lt;td>一致性證據&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這組機制把「交易正確性」前移到 API 與遷移設計，而不是事後 reconciliation 才補救。&lt;/p>
&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>duplicate request collapse ratio&lt;/td>
 &lt;td>重試是否被正確合併&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>migration phase error drift&lt;/td>
 &lt;td>遷移各階段錯誤是否收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>canary transaction anomaly&lt;/td>
 &lt;td>小流量交易是否出現偏差&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>payment trace consistency&lt;/td>
 &lt;td>trace 是否完整覆蓋交易關鍵欄位&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把 idempotency 實作成「只去重請求 ID」會漏掉交易語義。正確做法是讓 key 與業務操作邊界一致，並保留足夠證據以供重放與稽核判讀。另一個常見錯誤是把 migration 視為資料庫任務，沒有與 release gate 共同治理。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>實作層先從 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12&lt;/a> 定義重放語義，再到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a> 建立遷移節奏。發布控制對齊 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>；事故時的交易影響評估對齊 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Stripe 案例的核心責任是確保交易語義在重試與變更中保持一致。支付系統的失效成本不只來自停機，還來自錯誤結果；因此可靠性設計要同時守住可用性與正確性。</p>
<h2 id="問題場景">問題場景</h2>
<p>交易系統最常見的高風險組合是：客戶端重試、網路抖動、後端部署或資料遷移同時發生。若系統只處理單一失效，結果往往是可用但不一致，或者一致但無法持續交付。</p>
<p>idempotency key 與 zero-downtime migration 的組合，目標是讓這些變更在同一套邊界下可判讀。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Idempotency key</td>
          <td>同一交易重送如何得到同一結果</td>
          <td>重試安全</td>
      </tr>
      <tr>
          <td>Expand/contract migration</td>
          <td>資料變更如何與新舊版本共存</td>
          <td>漸進遷移</td>
      </tr>
      <tr>
          <td>Canary + rollback gate</td>
          <td>發版異常如何快速收斂</td>
          <td>可回復交付</td>
      </tr>
      <tr>
          <td>Transaction-path observability</td>
          <td>交易路徑是否可追溯</td>
          <td>一致性證據</td>
      </tr>
  </tbody>
</table>
<p>這組機制把「交易正確性」前移到 API 與遷移設計，而不是事後 reconciliation 才補救。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>duplicate request collapse ratio</td>
          <td>重試是否被正確合併</td>
          <td><a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12</a></td>
      </tr>
      <tr>
          <td>migration phase error drift</td>
          <td>遷移各階段錯誤是否收斂</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td>canary transaction anomaly</td>
          <td>小流量交易是否出現偏差</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>payment trace consistency</td>
          <td>trace 是否完整覆蓋交易關鍵欄位</td>
          <td><a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把 idempotency 實作成「只去重請求 ID」會漏掉交易語義。正確做法是讓 key 與業務操作邊界一致，並保留足夠證據以供重放與稽核判讀。另一個常見錯誤是把 migration 視為資料庫任務，沒有與 release gate 共同治理。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>實作層先從 <a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12</a> 定義重放語義，再到 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a> 建立遷移節奏。發布控制對齊 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a>；事故時的交易影響評估對齊 <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a>。</p>
]]></content:encoded></item><item><title>Meta：BGP 事故與控制面恢復順序</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/</guid><description>&lt;p>控制面恢復順序的責任是確保回復路徑不依賴已故障的系統。當 DNS、BGP、遠端存取工具與內部通訊都跑在同一個網路上，網路故障會同時切斷服務和回復手段。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>2021-10-04，Meta 的一次 BGP 配置變更導致骨幹網路撤回所有 route announcement。影響的範圍不只是對外服務：DNS 因為無法到達權威 DNS server 而失效，內部工具（包含遠端管理、通訊與身份驗證）也依賴同一個內部網路，因此同步不可用。&lt;/p>
&lt;p>工程師無法透過遠端存取工具連線到設備，必須實體前往資料中心手動恢復 BGP 配置。資料中心的實體存取流程（門禁授權、安全人員協調、設備定位）進一步拉長恢復時間。整個事故從發生到服務恢復超過 6 小時。&lt;/p>
&lt;p>這個事故的核心教訓是恢復工具必須獨立於被恢復的系統。當 out-of-band 路徑在設計上或認證上依賴 production 網路，它就不是真正的 out-of-band。&lt;/p>
&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>Out-of-band management&lt;/td>
 &lt;td>恢復路徑是否獨立於 production 網路&lt;/td>
 &lt;td>獨立連線與管理通道&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Recovery dependency mapping&lt;/td>
 &lt;td>每個回復步驟的依賴是否有循環&lt;/td>
 &lt;td>依賴圖與循環偵測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Staged recovery order&lt;/td>
 &lt;td>恢復順序是否先連通再服務&lt;/td>
 &lt;td>網路 → DNS → 控制面 → 資料面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Physical access readiness&lt;/td>
 &lt;td>remote 手段失效時實體存取是否可立即啟動&lt;/td>
 &lt;td>授權、存取卡、知識分佈&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Out-of-band management 的設計約束是完全獨立於 production 路徑。這包含網路連線（獨立 ISP 或 cellular）、認證（不依賴 production identity service）與通訊（獨立通訊工具或電話樹）。任何一環依賴 production 系統，就不算真正的 out-of-band。&lt;/p>
&lt;p>Recovery dependency mapping 的責任是在事故前畫出恢復步驟之間的依賴關係，找出循環依賴。Meta 事故中，DNS 恢復依賴網路連通，網路恢復依賴 BGP 設備存取，設備存取依賴 out-of-band 工具，而 out-of-band 工具的認證依賴 production identity service — 形成循環。事前的 dependency mapping 能暴露這類隱性路徑。&lt;/p>
&lt;p>Staged recovery order 把恢復拆成明確的階段：先恢復物理網路連通，再恢復 DNS 與名稱解析，接著恢復控制面服務（監控、部署、配置管理），最後恢復資料面流量。每個階段有明確的完成條件，下一階段才啟動。&lt;/p>
&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>out-of-band reachability&lt;/td>
 &lt;td>獨立管理通道是否可連線&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>recovery dependency cycle count&lt;/td>
 &lt;td>恢復步驟之間是否存在循環依賴&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DNS propagation lag&lt;/td>
 &lt;td>名稱解析恢復後多久全域生效&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>physical access activation time&lt;/td>
 &lt;td>從決策到實體接觸設備的時間&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>最常見的錯誤是把 out-of-band 存取當成「有設定就好」而不定期驗證。Meta 事故暴露的問題是 out-of-band 工具的 authentication 依賴 production identity service — 名義上路徑獨立，實際上認證路徑共享。DR rehearsal 必須包含「假設 production 網路完全不可用」的場景，驗證 out-of-band 路徑的每一環（連線、認證、通訊、操作權限）都能獨立運作。&lt;/p>
&lt;p>另一個常見問題是 recovery 知識集中在少數人。當實體恢復需要到場操作時，知識的地理分佈直接影響恢復時間。關鍵設備的恢復程序必須文件化，且分佈在多個地理位置的團隊成員手上。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal&lt;/a>：out-of-band 路徑的定期驗證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget&lt;/a>：恢復路徑的隱性依賴治理&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition&lt;/a>：DNS 與控制面恢復完成的判準&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14 multi-incident coordination&lt;/a>：跨區域恢復的指揮協調&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/">More details about the October 4 outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://engineering.fb.com/2021/10/04/networking-traffic/outage/">Update about the October 4th outage&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>控制面恢復順序的責任是確保回復路徑不依賴已故障的系統。當 DNS、BGP、遠端存取工具與內部通訊都跑在同一個網路上，網路故障會同時切斷服務和回復手段。</p>
<h2 id="問題場景">問題場景</h2>
<p>2021-10-04，Meta 的一次 BGP 配置變更導致骨幹網路撤回所有 route announcement。影響的範圍不只是對外服務：DNS 因為無法到達權威 DNS server 而失效，內部工具（包含遠端管理、通訊與身份驗證）也依賴同一個內部網路，因此同步不可用。</p>
<p>工程師無法透過遠端存取工具連線到設備，必須實體前往資料中心手動恢復 BGP 配置。資料中心的實體存取流程（門禁授權、安全人員協調、設備定位）進一步拉長恢復時間。整個事故從發生到服務恢復超過 6 小時。</p>
<p>這個事故的核心教訓是恢復工具必須獨立於被恢復的系統。當 out-of-band 路徑在設計上或認證上依賴 production 網路，它就不是真正的 out-of-band。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Out-of-band management</td>
          <td>恢復路徑是否獨立於 production 網路</td>
          <td>獨立連線與管理通道</td>
      </tr>
      <tr>
          <td>Recovery dependency mapping</td>
          <td>每個回復步驟的依賴是否有循環</td>
          <td>依賴圖與循環偵測</td>
      </tr>
      <tr>
          <td>Staged recovery order</td>
          <td>恢復順序是否先連通再服務</td>
          <td>網路 → DNS → 控制面 → 資料面</td>
      </tr>
      <tr>
          <td>Physical access readiness</td>
          <td>remote 手段失效時實體存取是否可立即啟動</td>
          <td>授權、存取卡、知識分佈</td>
      </tr>
  </tbody>
</table>
<p>Out-of-band management 的設計約束是完全獨立於 production 路徑。這包含網路連線（獨立 ISP 或 cellular）、認證（不依賴 production identity service）與通訊（獨立通訊工具或電話樹）。任何一環依賴 production 系統，就不算真正的 out-of-band。</p>
<p>Recovery dependency mapping 的責任是在事故前畫出恢復步驟之間的依賴關係，找出循環依賴。Meta 事故中，DNS 恢復依賴網路連通，網路恢復依賴 BGP 設備存取，設備存取依賴 out-of-band 工具，而 out-of-band 工具的認證依賴 production identity service — 形成循環。事前的 dependency mapping 能暴露這類隱性路徑。</p>
<p>Staged recovery order 把恢復拆成明確的階段：先恢復物理網路連通，再恢復 DNS 與名稱解析，接著恢復控制面服務（監控、部署、配置管理），最後恢復資料面流量。每個階段有明確的完成條件，下一階段才啟動。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>out-of-band reachability</td>
          <td>獨立管理通道是否可連線</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a></td>
      </tr>
      <tr>
          <td>recovery dependency cycle count</td>
          <td>恢復步驟之間是否存在循環依賴</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>DNS propagation lag</td>
          <td>名稱解析恢復後多久全域生效</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>physical access activation time</td>
          <td>從決策到實體接觸設備的時間</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>最常見的錯誤是把 out-of-band 存取當成「有設定就好」而不定期驗證。Meta 事故暴露的問題是 out-of-band 工具的 authentication 依賴 production identity service — 名義上路徑獨立，實際上認證路徑共享。DR rehearsal 必須包含「假設 production 網路完全不可用」的場景，驗證 out-of-band 路徑的每一環（連線、認證、通訊、操作權限）都能獨立運作。</p>
<p>另一個常見問題是 recovery 知識集中在少數人。當實體恢復需要到場操作時，知識的地理分佈直接影響恢復時間。關鍵設備的恢復程序必須文件化，且分佈在多個地理位置的團隊成員手上。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal</a>：out-of-band 路徑的定期驗證</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：恢復路徑的隱性依賴治理</li>
<li><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition</a>：DNS 與控制面恢復完成的判準</li>
<li><a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14 multi-incident coordination</a>：跨區域恢復的指揮協調</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/">More details about the October 4 outage</a></li>
<li><a href="https://engineering.fb.com/2021/10/04/networking-traffic/outage/">Update about the October 4th outage</a></li>
</ul>
]]></content:encoded></item><item><title>Pinterest：Storage Migration 與 Data Infrastructure Reliability</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/</guid><description>&lt;p>Storage migration 的可靠性責任是讓資料基礎設施的變更可漸進、可驗證、可回退。PB 級資料的儲存引擎遷移（如 HBase → TiDB）牽涉 schema mapping、query pattern 差異與 consistency model 變更，任何一處不相容都會在 production 流量下被放大。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>Pinterest 的資料基礎設施服務數十億 pin、推薦系統與搜尋索引。當儲存引擎需要退役或升級時，直接 cutover 的風險在於所有不相容同時暴露 — query 語意差異、pagination 行為、null handling、ordering 規則都可能在切換瞬間衝擊線上流量。&lt;/p>
&lt;p>漸進遷移的設計核心是把一次性 cutover 拆成可觀測的多階段流程，每個階段都有回退路徑。&lt;/p>
&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>Dual-write&lt;/td>
 &lt;td>新舊系統的寫入是否同步且完整&lt;/td>
 &lt;td>資料不遺失保證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shadow read&lt;/td>
 &lt;td>新舊系統的讀取結果是否一致&lt;/td>
 &lt;td>行為差異清單&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Reconciliation&lt;/td>
 &lt;td>兩套系統的資料是否持續一致&lt;/td>
 &lt;td>一致性報告&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Staged cutover&lt;/td>
 &lt;td>何時可以把流量從舊系統切到新系統&lt;/td>
 &lt;td>漸進切換節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Dual-write 確保遷移期間每筆寫入同時進入新舊系統。寫入失敗的處理策略決定資料完整性 — 若新系統寫入失敗是否 block 舊系統的寫入，取決於遷移階段（早期容許新系統 fail-open、接近 cutover 時需要 fail-close）。&lt;/p>
&lt;p>Shadow read 在真實流量下比對新舊系統的查詢結果。比對維度包含回傳資料的完整性、排序、分頁邊界與 null 值處理。mismatch rate 是 cutover 可行性的核心判準 — rate 趨近零才能進入下一批切換。&lt;/p>
&lt;p>Staged cutover 按 traffic percentage、data partition 或 use case 漸進切換。每一批觀察 mismatch rate、latency overhead 與 error rate，任一指標超門檻即回退到舊系統。&lt;/p>
&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>shadow read mismatch rate&lt;/td>
 &lt;td>新舊系統行為差異是否收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>dual-write latency overhead&lt;/td>
 &lt;td>同步寫入是否拖累主路徑&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>reconciliation gap&lt;/td>
 &lt;td>兩套系統資料是否持續一致&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cutover rollback count&lt;/td>
 &lt;td>切換過程是否穩定&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>Shadow read 比對容易只看最終結果是否相同，忽略中間狀態的差異。pagination 的邊界行為、null 欄位的回傳語意、排序在 tie-breaking 時的規則 — 這些差異在主流程不明顯，但在邊界情境會爆發。reconciliation 需要覆蓋 edge case，包含空集合回傳、大量資料分頁與 concurrent write 衝突。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety&lt;/a>：storage migration 的 schema 相容與 rollout 策略&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal&lt;/a>：cutover 失敗時的 rollback 路徑&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate&lt;/a>：dual-write latency 作為 regression 偵測&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff&lt;/a>：reconciliation 結果作為 cutover 決策證據&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/online-data-migration-from-hbase-to-tidb-with-zero-downtime-43f0fb474b84">Online Data Migration from HBase to TiDB with Zero Downtime&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/hbase-deprecation-at-pinterest-8a99e6c8e6b7">HBase Deprecation at Pinterest&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Storage migration 的可靠性責任是讓資料基礎設施的變更可漸進、可驗證、可回退。PB 級資料的儲存引擎遷移（如 HBase → TiDB）牽涉 schema mapping、query pattern 差異與 consistency model 變更，任何一處不相容都會在 production 流量下被放大。</p>
<h2 id="問題場景">問題場景</h2>
<p>Pinterest 的資料基礎設施服務數十億 pin、推薦系統與搜尋索引。當儲存引擎需要退役或升級時，直接 cutover 的風險在於所有不相容同時暴露 — query 語意差異、pagination 行為、null handling、ordering 規則都可能在切換瞬間衝擊線上流量。</p>
<p>漸進遷移的設計核心是把一次性 cutover 拆成可觀測的多階段流程，每個階段都有回退路徑。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Dual-write</td>
          <td>新舊系統的寫入是否同步且完整</td>
          <td>資料不遺失保證</td>
      </tr>
      <tr>
          <td>Shadow read</td>
          <td>新舊系統的讀取結果是否一致</td>
          <td>行為差異清單</td>
      </tr>
      <tr>
          <td>Reconciliation</td>
          <td>兩套系統的資料是否持續一致</td>
          <td>一致性報告</td>
      </tr>
      <tr>
          <td>Staged cutover</td>
          <td>何時可以把流量從舊系統切到新系統</td>
          <td>漸進切換節奏</td>
      </tr>
  </tbody>
</table>
<p>Dual-write 確保遷移期間每筆寫入同時進入新舊系統。寫入失敗的處理策略決定資料完整性 — 若新系統寫入失敗是否 block 舊系統的寫入，取決於遷移階段（早期容許新系統 fail-open、接近 cutover 時需要 fail-close）。</p>
<p>Shadow read 在真實流量下比對新舊系統的查詢結果。比對維度包含回傳資料的完整性、排序、分頁邊界與 null 值處理。mismatch rate 是 cutover 可行性的核心判準 — rate 趨近零才能進入下一批切換。</p>
<p>Staged cutover 按 traffic percentage、data partition 或 use case 漸進切換。每一批觀察 mismatch rate、latency overhead 與 error rate，任一指標超門檻即回退到舊系統。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>shadow read mismatch rate</td>
          <td>新舊系統行為差異是否收斂</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a></td>
      </tr>
      <tr>
          <td>dual-write latency overhead</td>
          <td>同步寫入是否拖累主路徑</td>
          <td><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13</a></td>
      </tr>
      <tr>
          <td>reconciliation gap</td>
          <td>兩套系統資料是否持續一致</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a></td>
      </tr>
      <tr>
          <td>cutover rollback count</td>
          <td>切換過程是否穩定</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>Shadow read 比對容易只看最終結果是否相同，忽略中間狀態的差異。pagination 的邊界行為、null 欄位的回傳語意、排序在 tie-breaking 時的規則 — 這些差異在主流程不明顯，但在邊界情境會爆發。reconciliation 需要覆蓋 edge case，包含空集合回傳、大量資料分頁與 concurrent write 衝突。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a>：storage migration 的 schema 相容與 rollout 策略</li>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rollback rehearsal</a>：cutover 失敗時的 rollback 路徑</li>
<li><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a>：dual-write latency 作為 regression 偵測</li>
<li><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff</a>：reconciliation 結果作為 cutover 決策證據</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/online-data-migration-from-hbase-to-tidb-with-zero-downtime-43f0fb474b84">Online Data Migration from HBase to TiDB with Zero Downtime</a></li>
<li><a href="https://medium.com/pinterest-engineering/hbase-deprecation-at-pinterest-8a99e6c8e6b7">HBase Deprecation at Pinterest</a></li>
</ul>
]]></content:encoded></item><item><title>Spotify：Backstage Service Catalog 與 Reliability Metadata</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/</guid><description>&lt;p>Service catalog 在可靠性工程中的責任是讓每個服務的 reliability metadata 有單一查詢入口。事故發生時，團隊能在同一個地方找到 owner、SLO 狀態、依賴圖與 runbook，而不是在 wiki、Slack 與個人筆記之間來回搜尋。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>Squad-based 組織結構讓團隊能獨立交付，但也讓服務數量快速增長。當服務超過數百個，metadata 開始散落在不同系統：ownership 記在 wiki、SLO 記在 monitoring 平台、runbook 記在文件庫、依賴關係靠口頭傳遞。事故時花時間找 owner 和 runbook 的成本直接拉長 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR&lt;/a>。Spotify 用 Backstage 作為 service catalog，把這些 metadata 收攏到同一個入口。&lt;/p>
&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>Service ownership&lt;/td>
 &lt;td>這個服務歸誰管&lt;/td>
 &lt;td>強制 owner team&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SLO metadata&lt;/td>
 &lt;td>這個服務的可靠性承諾是什麼&lt;/td>
 &lt;td>catalog 內嵌 SLO&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dependency graph&lt;/td>
 &lt;td>這個服務依賴誰、誰依賴它&lt;/td>
 &lt;td>可查詢依賴圖&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Runbook linkage&lt;/td>
 &lt;td>出事時該看哪份 runbook&lt;/td>
 &lt;td>一鍵連結&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metadata freshness&lt;/td>
 &lt;td>catalog 資料是否仍然準確&lt;/td>
 &lt;td>過期警告機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Service ownership 是最基礎的一層。每個服務在 catalog 中必須有明確的 owner team，沒有 owner 的服務標記為 orphan 並進入清理追蹤。ownership 不只是名義歸屬，而是事故時的第一接手責任。&lt;/p>
&lt;p>SLO metadata 讓 catalog 不只是目錄，而是可靠性狀態的即時入口。團隊能在 catalog 頁面直接看到服務目前的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget&lt;/a> 消耗狀態，判斷該服務的變更風險。&lt;/p>
&lt;p>Dependency graph 的價值在事故時最明顯。當一個服務異常時，catalog 能回答「還有誰會被影響」和「這個問題可能從哪裡傳過來」，讓事故指揮能快速判斷 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>。&lt;/p>
&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>Orphan service count&lt;/td>
 &lt;td>無 owner 服務是否持續增加&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metadata freshness&lt;/td>
 &lt;td>catalog 資料是否仍然準確&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dependency coverage&lt;/td>
 &lt;td>依賴圖是否涵蓋關鍵路徑&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MTTR vs catalog coverage&lt;/td>
 &lt;td>catalog 覆蓋率是否與恢復速度相關&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>Catalog 最常見的失效模式是變成靜態文件。若 metadata 靠人工維護但沒有 freshness check，catalog 會隨時間漂移 — owner 換了團隊但 catalog 沒更新、SLO 調整了但 catalog 還是舊值、依賴關係變了但 graph 沒有同步。事故時從 catalog 拿到過期資訊，比沒有 catalog 更危險，因為團隊會信任它。維持 catalog 價值的關鍵是自動化校驗：定期掃描 orphan service、比對 SLO metadata 與 monitoring 平台的實際值、用 runtime trace 驗證依賴圖的準確性。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget&lt;/a>：catalog 的依賴圖是 dependency budget 的資料來源&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 reliability metrics governance&lt;/a>：catalog coverage 與 metadata freshness 本身是可靠性指標&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review&lt;/a>：readiness checklist 可從 catalog 自動拉取&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog&lt;/a>：orphan service 與過期 metadata 是 reliability debt&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://backstage.io/">Backstage.io&lt;/a>：Spotify 開源的 developer portal 框架&lt;/li>
&lt;li>&lt;a href="https://backstage.spotify.com/">Spotify Engineering: What is Backstage?&lt;/a>：Backstage 的設計理念與架構&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Service catalog 在可靠性工程中的責任是讓每個服務的 reliability metadata 有單一查詢入口。事故發生時，團隊能在同一個地方找到 owner、SLO 狀態、依賴圖與 runbook，而不是在 wiki、Slack 與個人筆記之間來回搜尋。</p>
<h2 id="問題場景">問題場景</h2>
<p>Squad-based 組織結構讓團隊能獨立交付，但也讓服務數量快速增長。當服務超過數百個，metadata 開始散落在不同系統：ownership 記在 wiki、SLO 記在 monitoring 平台、runbook 記在文件庫、依賴關係靠口頭傳遞。事故時花時間找 owner 和 runbook 的成本直接拉長 <a href="/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR</a>。Spotify 用 Backstage 作為 service catalog，把這些 metadata 收攏到同一個入口。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Service ownership</td>
          <td>這個服務歸誰管</td>
          <td>強制 owner team</td>
      </tr>
      <tr>
          <td>SLO metadata</td>
          <td>這個服務的可靠性承諾是什麼</td>
          <td>catalog 內嵌 SLO</td>
      </tr>
      <tr>
          <td>Dependency graph</td>
          <td>這個服務依賴誰、誰依賴它</td>
          <td>可查詢依賴圖</td>
      </tr>
      <tr>
          <td>Runbook linkage</td>
          <td>出事時該看哪份 runbook</td>
          <td>一鍵連結</td>
      </tr>
      <tr>
          <td>Metadata freshness</td>
          <td>catalog 資料是否仍然準確</td>
          <td>過期警告機制</td>
      </tr>
  </tbody>
</table>
<p>Service ownership 是最基礎的一層。每個服務在 catalog 中必須有明確的 owner team，沒有 owner 的服務標記為 orphan 並進入清理追蹤。ownership 不只是名義歸屬，而是事故時的第一接手責任。</p>
<p>SLO metadata 讓 catalog 不只是目錄，而是可靠性狀態的即時入口。團隊能在 catalog 頁面直接看到服務目前的 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 消耗狀態，判斷該服務的變更風險。</p>
<p>Dependency graph 的價值在事故時最明顯。當一個服務異常時，catalog 能回答「還有誰會被影響」和「這個問題可能從哪裡傳過來」，讓事故指揮能快速判斷 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Orphan service count</td>
          <td>無 owner 服務是否持續增加</td>
          <td><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a></td>
      </tr>
      <tr>
          <td>Metadata freshness</td>
          <td>catalog 資料是否仍然準確</td>
          <td><a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18</a></td>
      </tr>
      <tr>
          <td>Dependency coverage</td>
          <td>依賴圖是否涵蓋關鍵路徑</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>MTTR vs catalog coverage</td>
          <td>catalog 覆蓋率是否與恢復速度相關</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>Catalog 最常見的失效模式是變成靜態文件。若 metadata 靠人工維護但沒有 freshness check，catalog 會隨時間漂移 — owner 換了團隊但 catalog 沒更新、SLO 調整了但 catalog 還是舊值、依賴關係變了但 graph 沒有同步。事故時從 catalog 拿到過期資訊，比沒有 catalog 更危險，因為團隊會信任它。維持 catalog 價值的關鍵是自動化校驗：定期掃描 orphan service、比對 SLO metadata 與 monitoring 平台的實際值、用 runtime trace 驗證依賴圖的準確性。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：catalog 的依賴圖是 dependency budget 的資料來源</li>
<li><a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 reliability metrics governance</a>：catalog coverage 與 metadata freshness 本身是可靠性指標</li>
<li><a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a>：readiness checklist 可從 catalog 自動拉取</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：orphan service 與過期 metadata 是 reliability debt</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://backstage.io/">Backstage.io</a>：Spotify 開源的 developer portal 框架</li>
<li><a href="https://backstage.spotify.com/">Spotify Engineering: What is Backstage?</a>：Backstage 的設計理念與架構</li>
</ul>
]]></content:encoded></item><item><title>Stripe：Canary Deploy 與 Progressive Rollout 治理</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/</guid><description>&lt;p>金流場景的 canary deploy 核心責任是讓每一批放量都能用交易指標判斷是否安全。progressive rollout 的節奏由交易成功率、duplicate charge 偵測與退款異常等金流特有指標驅動。本文從金流場景的通用壓力推導 progressive rollout 設計，以 Stripe 公開的 deploy 與 idempotency 實踐作為背景脈絡。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>金流變更的風險帶有延遲性。交易失敗可能在結帳時才被發現，退款申請可能在數天後才出現，對帳差異可能在日終結算才暴露。若 canary 只觀察幾分鐘的 error rate，延遲暴露的問題會在全量放行後才浮現。&lt;/p>
&lt;p>這種延遲特性讓金流場景需要比一般功能更長的觀察窗與更多元的判讀指標。放行決策要等交易生命週期的關鍵階段都走過，才能確認變更安全。&lt;/p>
&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>Canary traffic control&lt;/td>
 &lt;td>每批流量比例與觀察窗如何設定&lt;/td>
 &lt;td>1% → 5% → 25% → 100%，觀察窗依交易確認延遲調整&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Transaction-specific checks&lt;/td>
 &lt;td>交易指標是否涵蓋結帳到對帳的完整鏈路&lt;/td>
 &lt;td>checkout success rate、capture rate、duplicate、refund anomaly&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Automatic rollback trigger&lt;/td>
 &lt;td>交易異常時是否能即時回退&lt;/td>
 &lt;td>指標超門檻自動回退，不等人工判斷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Staged config vs code&lt;/td>
 &lt;td>config 變更與 code 變更的風險是否相同&lt;/td>
 &lt;td>timeout / retry 等 config 變更走獨立且更短的 rollout 節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Canary traffic 的觀察窗設計是這個機制的關鍵。1% 階段至少觀察到一個完整的交易確認週期（通常 30 分鐘到數小時），5% 階段需要覆蓋一個對帳週期，25% 階段需要確認退款率無異常。每批之間的 go/no-go 判斷依據是全部交易指標都在 baseline 範圍內，任一指標偏離即暫停擴批。&lt;/p>
&lt;p>Config 變更（如 provider timeout 或 retry 次數）與 code 變更走不同 rollout 路線。config 變更影響面通常更可預測、回退更快（秒級生效），但風險在於小幅調整也可能放大 retry storm 或觸發 cascade timeout。&lt;/p>
&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>checkout success rate&lt;/td>
 &lt;td>canary 批次是否維持交易承諾&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>canary vs baseline latency&lt;/td>
 &lt;td>延遲偏移是否超過可接受範圍&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>payment duplicate rate&lt;/td>
 &lt;td>重試是否產生重複扣款&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollback trigger count&lt;/td>
 &lt;td>自動回退是否頻繁觸發&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>refund anomaly rate&lt;/td>
 &lt;td>退款比率是否偏離歷史 baseline&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把金流 canary 跟一般 feature rollout 用同一套觀察窗，會漏掉延遲暴露的問題。金流的 feedback loop 從結帳到退款可能跨越數天，短窗觀察拿到的 pass 訊號只代表即時指標正常，無法涵蓋對帳與退款階段的風險。&lt;/p>
&lt;p>另一個常見問題是 config 變更被視為低風險而跳過 canary。timeout 或 retry 設定的微幅調整看似無害，但在高流量下可能觸發 retry storm 或改變 provider 端的行為，影響幅度可能大於 code 變更。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate&lt;/a> 定義金流場景的放行政策，再到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 Feature Flag Governance&lt;/a> 設計 progressive rollout 的 flag lifecycle。實作示範見 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/provider-dependency-release-gate/" data-link-title="6.25 Provider Dependency Release Gate 實作示範" data-link-desc="以 payment provider 變更示範 release gate 如何結合 evidence、stop condition 與 rollback window。">6.25 Provider Dependency Release Gate&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>金流場景的 canary deploy 核心責任是讓每一批放量都能用交易指標判斷是否安全。progressive rollout 的節奏由交易成功率、duplicate charge 偵測與退款異常等金流特有指標驅動。本文從金流場景的通用壓力推導 progressive rollout 設計，以 Stripe 公開的 deploy 與 idempotency 實踐作為背景脈絡。</p>
<h2 id="問題場景">問題場景</h2>
<p>金流變更的風險帶有延遲性。交易失敗可能在結帳時才被發現，退款申請可能在數天後才出現，對帳差異可能在日終結算才暴露。若 canary 只觀察幾分鐘的 error rate，延遲暴露的問題會在全量放行後才浮現。</p>
<p>這種延遲特性讓金流場景需要比一般功能更長的觀察窗與更多元的判讀指標。放行決策要等交易生命週期的關鍵階段都走過，才能確認變更安全。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>控制方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Canary traffic control</td>
          <td>每批流量比例與觀察窗如何設定</td>
          <td>1% → 5% → 25% → 100%，觀察窗依交易確認延遲調整</td>
      </tr>
      <tr>
          <td>Transaction-specific checks</td>
          <td>交易指標是否涵蓋結帳到對帳的完整鏈路</td>
          <td>checkout success rate、capture rate、duplicate、refund anomaly</td>
      </tr>
      <tr>
          <td>Automatic rollback trigger</td>
          <td>交易異常時是否能即時回退</td>
          <td>指標超門檻自動回退，不等人工判斷</td>
      </tr>
      <tr>
          <td>Staged config vs code</td>
          <td>config 變更與 code 變更的風險是否相同</td>
          <td>timeout / retry 等 config 變更走獨立且更短的 rollout 節奏</td>
      </tr>
  </tbody>
</table>
<p>Canary traffic 的觀察窗設計是這個機制的關鍵。1% 階段至少觀察到一個完整的交易確認週期（通常 30 分鐘到數小時），5% 階段需要覆蓋一個對帳週期，25% 階段需要確認退款率無異常。每批之間的 go/no-go 判斷依據是全部交易指標都在 baseline 範圍內，任一指標偏離即暫停擴批。</p>
<p>Config 變更（如 provider timeout 或 retry 次數）與 code 變更走不同 rollout 路線。config 變更影響面通常更可預測、回退更快（秒級生效），但風險在於小幅調整也可能放大 retry storm 或觸發 cascade timeout。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>checkout success rate</td>
          <td>canary 批次是否維持交易承諾</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>canary vs baseline latency</td>
          <td>延遲偏移是否超過可接受範圍</td>
          <td><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13</a></td>
      </tr>
      <tr>
          <td>payment duplicate rate</td>
          <td>重試是否產生重複扣款</td>
          <td><a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12</a></td>
      </tr>
      <tr>
          <td>rollback trigger count</td>
          <td>自動回退是否頻繁觸發</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a></td>
      </tr>
      <tr>
          <td>refund anomaly rate</td>
          <td>退款比率是否偏離歷史 baseline</td>
          <td><a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把金流 canary 跟一般 feature rollout 用同一套觀察窗，會漏掉延遲暴露的問題。金流的 feedback loop 從結帳到退款可能跨越數天，短窗觀察拿到的 pass 訊號只代表即時指標正常，無法涵蓋對帳與退款階段的風險。</p>
<p>另一個常見問題是 config 變更被視為低風險而跳過 canary。timeout 或 retry 設定的微幅調整看似無害，但在高流量下可能觸發 retry storm 或改變 provider 端的行為，影響幅度可能大於 code 變更。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先回到 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a> 定義金流場景的放行政策，再到 <a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 Feature Flag Governance</a> 設計 progressive rollout 的 flag lifecycle。實作示範見 <a href="/blog/backend/06-reliability/provider-dependency-release-gate/" data-link-title="6.25 Provider Dependency Release Gate 實作示範" data-link-desc="以 payment provider 變更示範 release gate 如何結合 evidence、stop condition 與 rollback window。">6.25 Provider Dependency Release Gate</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://stripe.com/blog/idempotency">Designing robust and predictable APIs with idempotency</a>：idempotency key 設計，支撐 canary 回退後的重試安全</li>
<li><a href="https://stripe.com/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations">How Stripe&rsquo;s document databases supported 99.999% uptime with zero-downtime data migrations</a>：zero-downtime migration 的 staged rollout 思路</li>
</ul>
<p>本文的 progressive rollout 機制（觀察窗設計、交易指標門檻、自動回退）從金流場景的通用壓力推導，並非 Stripe 公開的具體 deploy pipeline 描述。</p>
]]></content:encoded></item><item><title>3.C42 Bitso：Reliable Redis Streams 抽象 + 自建 DLQ</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 沒有原生 DLQ、要在 application 層自建抽象。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Bitso 的 Order Engine 微服務需要 thousands of messages/sec/stream + 亞毫秒延遲、撐住 BTC 價格暴動的流量尖峰；先後評估 Kafka（latency）跟 SQS（vendor lock-in + latency）後選 Redis Streams、團隊本來就熟 Redis、已在 mission-critical service 跑超過半年。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 &amp;ldquo;Reliable Redis Streams&amp;rdquo; 抽象層（StreamRedisOperations adapter / ReliableStream interface / MessageReadingLoop）封裝 readMessages + readPendingMessages、加上 Redis Streams 沒有原生支援的 DLQ（N 次 retry 後路由）、走 idempotent processing 接受重複勝過遺失。揭露 Redis Streams 是「資料結構」、不是「broker 系統」、可靠性責任在 application 層。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Sentinel + Cluster 可靠性。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/bitso-engineering/the-redis-streams-we-have-known-and-loved-e9e596d49a22">The Redis Streams We Have Known and Loved&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 沒有原生 DLQ、要在 application 層自建抽象。</p>
<h2 id="觀察">觀察</h2>
<p>Bitso 的 Order Engine 微服務需要 thousands of messages/sec/stream + 亞毫秒延遲、撐住 BTC 價格暴動的流量尖峰；先後評估 Kafka（latency）跟 SQS（vendor lock-in + latency）後選 Redis Streams、團隊本來就熟 Redis、已在 mission-critical service 跑超過半年。</p>
<h2 id="判讀">判讀</h2>
<p>自建 &ldquo;Reliable Redis Streams&rdquo; 抽象層（StreamRedisOperations adapter / ReliableStream interface / MessageReadingLoop）封裝 readMessages + readPendingMessages、加上 Redis Streams 沒有原生支援的 DLQ（N 次 retry 後路由）、走 idempotent processing 接受重複勝過遺失。揭露 Redis Streams 是「資料結構」、不是「broker 系統」、可靠性責任在 application 層。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Sentinel + Cluster 可靠性。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/bitso-engineering/the-redis-streams-we-have-known-and-loved-e9e596d49a22">The Redis Streams We Have Known and Loved</a></li>
</ul>
]]></content:encoded></item><item><title>3.C43 Arcjet：Redis Streams 取代 Kafka 省 6 位數 $</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/</guid><description>&lt;p>Arcjet 用 Redis Streams 取代 Kafka 的案例揭露了中小規模場景下「Kafka 的 managed 成本 vs Redis Streams 的運維成本」的具體取捨 — 省下六位數年費的代價是自寫 retention 治理跟監控工具。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Arcjet 是 security / bot detection 平台，處理每個 HTTP request 的安全判斷。核心需求是 low-latency 的請求處理 — 安全判斷要在幾毫秒內完成，不能拖慢使用者的 request。&lt;/p>
&lt;p>系統架構中有一段 event-driven pipeline 負責把安全事件從 detection layer 傳遞到 analytics 跟 alerting。原本評估用 Kafka 做這段 pipeline，但 managed Kafka 的年費落在六位數美金 — 對 Arcjet 的流量規模跟業務階段，這個成本不合理。&lt;/p>
&lt;p>Arcjet 的基礎設施已經有 Redis 做 cache。把 Redis 從純 cache 升級到 cache + Streams，利用既有的 Redis infrastructure 承擔 event pipeline，總成本約 $1k/year。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="redis-streams-沒有自動-retention">Redis Streams 沒有自動 retention&lt;/h3>
&lt;p>Kafka 的 retention 是內建功能 — 設定 &lt;code>log.retention.hours&lt;/code> 後 broker 自動刪除到期資料。Redis Streams 沒有內建的自動 retention — stream 資料會持續累積，直到手動 &lt;code>XTRIM&lt;/code> 或 &lt;code>XDEL&lt;/code>。&lt;/p>
&lt;p>在生產環境下，不處理 retention 意味著 Redis 的記憶體持續成長，最終觸發 eviction policy 或 OOM。對 Arcjet 來說 Redis 同時做 cache 跟 Streams，Streams 的記憶體成長會擠壓 cache 的可用空間。&lt;/p>
&lt;h3 id="consumer-group-進度追蹤">Consumer group 進度追蹤&lt;/h3>
&lt;p>Redis Streams 的 consumer group 會追蹤每個 consumer 的讀取進度（last delivered ID）。做 &lt;code>XTRIM&lt;/code> 時需要確保不刪除尚未被所有 consumer group 確認的訊息 — 否則 consumer 會丟失未處理的事件。&lt;/p>
&lt;p>Kafka 的 log compaction 跟 retention 自動處理這個問題（consumer offset 以前的 segment 才會被清理）。Redis Streams 需要 application 自己確認所有 consumer group 的進度，再決定 trim 的位置。&lt;/p>
&lt;h3 id="單機-redis-的可靠性邊界">單機 Redis 的可靠性邊界&lt;/h3>
&lt;p>Redis 的持久化機制（RDB snapshot + AOF）提供的是 best-effort 的持久性，跟 Kafka 的 replication-based 持久化保證不同。Redis crash + restart 時，AOF 的最後幾筆寫入可能遺失（取決於 &lt;code>appendfsync&lt;/code> 設定）。&lt;/p>
&lt;p>對 Arcjet 的安全事件場景，偶爾丟失幾筆事件可以接受（security detection 的結果是即時判斷，事後的 analytics 容忍小量遺失）。如果場景是金融交易或 audit log，這個可靠性邊界就不夠。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="自建-janitor-process">自建 Janitor process&lt;/h3>
&lt;p>Arcjet 自寫了一個 Janitor process 處理 Redis Streams 的 retention：&lt;/p></description><content:encoded><![CDATA[<p>Arcjet 用 Redis Streams 取代 Kafka 的案例揭露了中小規模場景下「Kafka 的 managed 成本 vs Redis Streams 的運維成本」的具體取捨 — 省下六位數年費的代價是自寫 retention 治理跟監控工具。</p>
<h2 id="業務背景">業務背景</h2>
<p>Arcjet 是 security / bot detection 平台，處理每個 HTTP request 的安全判斷。核心需求是 low-latency 的請求處理 — 安全判斷要在幾毫秒內完成，不能拖慢使用者的 request。</p>
<p>系統架構中有一段 event-driven pipeline 負責把安全事件從 detection layer 傳遞到 analytics 跟 alerting。原本評估用 Kafka 做這段 pipeline，但 managed Kafka 的年費落在六位數美金 — 對 Arcjet 的流量規模跟業務階段，這個成本不合理。</p>
<p>Arcjet 的基礎設施已經有 Redis 做 cache。把 Redis 從純 cache 升級到 cache + Streams，利用既有的 Redis infrastructure 承擔 event pipeline，總成本約 $1k/year。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="redis-streams-沒有自動-retention">Redis Streams 沒有自動 retention</h3>
<p>Kafka 的 retention 是內建功能 — 設定 <code>log.retention.hours</code> 後 broker 自動刪除到期資料。Redis Streams 沒有內建的自動 retention — stream 資料會持續累積，直到手動 <code>XTRIM</code> 或 <code>XDEL</code>。</p>
<p>在生產環境下，不處理 retention 意味著 Redis 的記憶體持續成長，最終觸發 eviction policy 或 OOM。對 Arcjet 來說 Redis 同時做 cache 跟 Streams，Streams 的記憶體成長會擠壓 cache 的可用空間。</p>
<h3 id="consumer-group-進度追蹤">Consumer group 進度追蹤</h3>
<p>Redis Streams 的 consumer group 會追蹤每個 consumer 的讀取進度（last delivered ID）。做 <code>XTRIM</code> 時需要確保不刪除尚未被所有 consumer group 確認的訊息 — 否則 consumer 會丟失未處理的事件。</p>
<p>Kafka 的 log compaction 跟 retention 自動處理這個問題（consumer offset 以前的 segment 才會被清理）。Redis Streams 需要 application 自己確認所有 consumer group 的進度，再決定 trim 的位置。</p>
<h3 id="單機-redis-的可靠性邊界">單機 Redis 的可靠性邊界</h3>
<p>Redis 的持久化機制（RDB snapshot + AOF）提供的是 best-effort 的持久性，跟 Kafka 的 replication-based 持久化保證不同。Redis crash + restart 時，AOF 的最後幾筆寫入可能遺失（取決於 <code>appendfsync</code> 設定）。</p>
<p>對 Arcjet 的安全事件場景，偶爾丟失幾筆事件可以接受（security detection 的結果是即時判斷，事後的 analytics 容忍小量遺失）。如果場景是金融交易或 audit log，這個可靠性邊界就不夠。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="自建-janitor-process">自建 Janitor process</h3>
<p>Arcjet 自寫了一個 Janitor process 處理 Redis Streams 的 retention：</p>
<ol>
<li>定期檢查每個 stream 的長度（<code>XLEN</code>）</li>
<li>查詢所有 consumer group 的 pending entry list（PEL）跟最後確認位置</li>
<li>計算安全的 trim 位置（所有 consumer group 都已確認的最舊 ID）</li>
<li>執行 <code>XTRIM stream MINID &lt;safe-id&gt;</code> 刪除已確認的舊資料</li>
</ol>
<p>Janitor 的執行頻率根據實際處理速度（~100 msgs/min）設定 — 不需要非常頻繁，但不能完全不跑。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Managed Kafka</th>
          <th>Redis Streams + Janitor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年成本</td>
          <td>六位數 USD</td>
          <td>~$1k USD</td>
      </tr>
      <tr>
          <td>Retention 管理</td>
          <td>內建自動</td>
          <td>自寫 Janitor</td>
      </tr>
      <tr>
          <td>持久化保證</td>
          <td>Replication-based（強）</td>
          <td>AOF/RDB（best-effort）</td>
      </tr>
      <tr>
          <td>Consumer group</td>
          <td>原生支援、offset commit 自動</td>
          <td>原生支援、但 trim 要手動協調</td>
      </tr>
      <tr>
          <td>生態工具</td>
          <td>Kafka Connect、Schema Registry</td>
          <td>無（自建）</td>
      </tr>
      <tr>
          <td>擴展性</td>
          <td>Partition 水平擴展</td>
          <td>單 Redis 受限、Cluster 模式複雜</td>
      </tr>
      <tr>
          <td>運維知識</td>
          <td>Kafka 運維（或交給 managed）</td>
          <td>Redis 運維 + 自建 Janitor 維護</td>
      </tr>
  </tbody>
</table>
<h3 id="適用邊界">適用邊界</h3>
<p>Redis Streams 取代 Kafka 的適用邊界：</p>
<ul>
<li><strong>流量規模</strong>：每分鐘數百到數千筆（超過每秒數萬筆需要 Redis Cluster 或多 stream）</li>
<li><strong>持久化要求</strong>：容忍偶爾丟失少量訊息（best-effort）</li>
<li><strong>已有 Redis</strong>：不需要額外部署 Redis、利用既有 infrastructure</li>
<li><strong>Kafka 功能不需要</strong>：不需要 Kafka Connect、Schema Registry、long-term retention、跨 region replication</li>
</ul>
<p>超過這些邊界時，Redis Streams 的自建成本（Janitor + 監控 + retention 治理 + 可靠性補償）會逐漸接近 managed Kafka 的費用，成本優勢消失。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a>：XCLAIM / PEL recovery 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：成本對照 — Kafka 的固定成本高但功能完整</li>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：Redis Streams 的持久化機制跟 Kafka 的 replication 在 durability 光譜上的位置</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 選型時成本是一級決策維度</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>Managed Kafka 的月帳單跟實際流量量級不成比例（低流量但高成本）</li>
<li>已有 Redis infrastructure、考慮把 event pipeline 合併到 Redis</li>
<li>Event pipeline 的流量在每秒數百筆以下、持久化要求是 best-effort</li>
<li>Redis 記憶體持續成長但不確定 Streams 的 retention 有沒有正確執行</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.arcjet.com/replacing-kafka-with-redis-streams/">Replacing Kafka with Redis Streams</a></li>
</ul>
]]></content:encoded></item><item><title>3.C44 Harness：CD 微服務 async state transfer</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 在 production 落地的三類經常性議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Harness 為 CD 微服務之間的 async state transfer 採用 Redis Streams、避開「每個 service 都要知道怎麼跟其他 service 講話」的 brittle HTTP 模式；初始規模 a few thousand msgs/min、Kafka 在此規模 overkill、又能複用已存在的 Redis 基建。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>落地後揭露三類問題：監控缺口（自寫 app 追 consumer lag）、需要主動 MAXLEN truncation、head-of-line blocking 要用 XAUTOCLAIM 重派並設計 redelivery 策略。揭露「Redis Streams 適合中小規模」這個聲明、實際包含三件 production work。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Memory + retention 取捨。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.harness.io/blog/event-driven-architecture-redis-streams">Event-Driven Architecture with Redis Streams&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 在 production 落地的三類經常性議題。</p>
<h2 id="觀察">觀察</h2>
<p>Harness 為 CD 微服務之間的 async state transfer 採用 Redis Streams、避開「每個 service 都要知道怎麼跟其他 service 講話」的 brittle HTTP 模式；初始規模 a few thousand msgs/min、Kafka 在此規模 overkill、又能複用已存在的 Redis 基建。</p>
<h2 id="判讀">判讀</h2>
<p>落地後揭露三類問題：監控缺口（自寫 app 追 consumer lag）、需要主動 MAXLEN truncation、head-of-line blocking 要用 XAUTOCLAIM 重派並設計 redelivery 策略。揭露「Redis Streams 適合中小規模」這個聲明、實際包含三件 production work。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Memory + retention 取捨。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.harness.io/blog/event-driven-architecture-redis-streams">Event-Driven Architecture with Redis Streams</a></li>
</ul>
]]></content:encoded></item><item><title>3.C45 Klaxit：Rust + Redis Streams 處理 Heroku Logplex</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 在高吞吐 log ingestion 的 consumer group 分流。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Klaxit 用 Redis Streams 處理 Heroku Logplex 匯流的 log、自動偵測並修復 Heroku 平台層 perf 問題（在使用者察覺前）；正式 production 跑超過 6 個月、是團隊第一個 Rust project。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露 high-throughput log ingestion 對 Redis Streams 的壓力：用 consumer group 分流到多個 Rust worker、需要長時間穩定運轉。揭露 client library 品質決定 Redis Streams 在小眾語言（Rust）的可行性。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：XADD / XREAD / XREADGROUP 操作 / Consumer group + PEL。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/goodtouch/consuming-high-throughput-redis-streams-with-rust-580c">Consuming High-Throughput Redis Streams with Rust&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 在高吞吐 log ingestion 的 consumer group 分流。</p>
<h2 id="觀察">觀察</h2>
<p>Klaxit 用 Redis Streams 處理 Heroku Logplex 匯流的 log、自動偵測並修復 Heroku 平台層 perf 問題（在使用者察覺前）；正式 production 跑超過 6 個月、是團隊第一個 Rust project。</p>
<h2 id="判讀">判讀</h2>
<p>揭露 high-throughput log ingestion 對 Redis Streams 的壓力：用 consumer group 分流到多個 Rust worker、需要長時間穩定運轉。揭露 client library 品質決定 Redis Streams 在小眾語言（Rust）的可行性。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：XADD / XREAD / XREADGROUP 操作 / Consumer group + PEL。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://dev.to/goodtouch/consuming-high-throughput-redis-streams-with-rust-580c">Consuming High-Throughput Redis Streams with Rust</a></li>
</ul>
]]></content:encoded></item><item><title>3.C46 Learning.com：Redis 事件源退場（反例）</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/</guid><description>&lt;p>這個反例的核心責任是說明 Redis 不適合長期事件儲存、揭露「Redis-as-event-store」的退場路徑。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Learning.com 把 microservice 之間的 event store 放 Redis 上、一年內累積到 GB/週的 memory 成長、AOF fsync + EBS 磁碟 I/O 變成 latency 痛點。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露「Redis 不適合長期事件儲存」的退場路徑：event 移到 PostgreSQL、Redis 留做訊息佇列 + snapshot；中途靠 syncTimeout 調整、提升 IOPS、調整 AOF fsync 緩解。揭露 broker 選型要看「長期存儲是 source-of-truth 還是 transient」。&lt;strong>注意&lt;/strong>：此文討論的是 Redis-as-event-store 整體、Streams 是其中一塊、引用時要小心區分。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Memory + retention 取捨 / Sentinel + Cluster 可靠性（持久化選型）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/lcom-techblog/a-year-with-redis-event-sourcing-lessons-learned-6736068e17cc">A Year with Redis Event Sourcing - Lessons Learned&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 Redis 不適合長期事件儲存、揭露「Redis-as-event-store」的退場路徑。</p>
<h2 id="觀察">觀察</h2>
<p>Learning.com 把 microservice 之間的 event store 放 Redis 上、一年內累積到 GB/週的 memory 成長、AOF fsync + EBS 磁碟 I/O 變成 latency 痛點。</p>
<h2 id="判讀">判讀</h2>
<p>揭露「Redis 不適合長期事件儲存」的退場路徑：event 移到 PostgreSQL、Redis 留做訊息佇列 + snapshot；中途靠 syncTimeout 調整、提升 IOPS、調整 AOF fsync 緩解。揭露 broker 選型要看「長期存儲是 source-of-truth 還是 transient」。<strong>注意</strong>：此文討論的是 Redis-as-event-store 整體、Streams 是其中一塊、引用時要小心區分。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Memory + retention 取捨 / Sentinel + Cluster 可靠性（持久化選型）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/lcom-techblog/a-year-with-redis-event-sourcing-lessons-learned-6736068e17cc">A Year with Redis Event Sourcing - Lessons Learned</a></li>
</ul>
]]></content:encoded></item><item><title>3.C47 PHP 微服務：Redis Streams + S3 hybrid storage</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/</guid><description>&lt;p>這個案例的核心責任是說明 in-memory 訊息的 payload 限制要靠 hybrid storage 解決。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PHP 雙微服務之間的可靠通訊、Kafka 在 PHP 生態工具薄弱、團隊無 Kafka 經驗、production 跑數月後寫此文；明確覆蓋 XADD / XREADGROUP / consumer group / MAXLEN / MINID / XDEL / XACK / XACKDEL（Redis 8.2+）/ XTRIM。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露 in-memory 訊息的 payload 限制：用 payload compression + S3 hybrid storage（大 payload 存 S3、stream 只放 reference）；用 MAXLEN/MINID 控制 stream 成長。揭露 broker 選型常被「語言生態 client 品質」主導、不是純技術 feature。&lt;strong>注意&lt;/strong>：作者是個人工程師、production 經驗但非知名公司。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：XADD/XREAD/XREADGROUP 操作 / Retention (MAXLEN/MINID) / Memory + retention 取捨。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16 Robinhood Faust&lt;/a>（語言生態對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc">Beyond the Hype: Why We Chose Redis Streams Over Kafka for Our Microservices&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 in-memory 訊息的 payload 限制要靠 hybrid storage 解決。</p>
<h2 id="觀察">觀察</h2>
<p>PHP 雙微服務之間的可靠通訊、Kafka 在 PHP 生態工具薄弱、團隊無 Kafka 經驗、production 跑數月後寫此文；明確覆蓋 XADD / XREADGROUP / consumer group / MAXLEN / MINID / XDEL / XACK / XACKDEL（Redis 8.2+）/ XTRIM。</p>
<h2 id="判讀">判讀</h2>
<p>揭露 in-memory 訊息的 payload 限制：用 payload compression + S3 hybrid storage（大 payload 存 S3、stream 只放 reference）；用 MAXLEN/MINID 控制 stream 成長。揭露 broker 選型常被「語言生態 client 品質」主導、不是純技術 feature。<strong>注意</strong>：作者是個人工程師、production 經驗但非知名公司。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：XADD/XREAD/XREADGROUP 操作 / Retention (MAXLEN/MINID) / Memory + retention 取捨。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16 Robinhood Faust</a>（語言生態對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://dev.to/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc">Beyond the Hype: Why We Chose Redis Streams Over Kafka for Our Microservices</a></li>
</ul>
]]></content:encoded></item><item><title>3.C48 Airbnb Dynein：SQS 分散式延遲任務排程</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/</guid><description>&lt;p>這個案例的核心責任是說明 SQS at-least-once + DLQ 模型在工作排程的對齊邏輯。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 構建 Dynein 分散式延遲任務排程系統取代 Resque（受限於單 Redis 實例）。明確選 SQS、利用 at-least-once delivery、dead letter queue、individual message acknowledgment、access control 與 encryption-at-rest。每個 scheduler instance 達 ~1000 QPS、可水平擴展。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>at-least-once 對工作排程「不丟資料」假設足夠、SQS wrap DynamoDB 處理 &amp;gt; 15 分鐘 delay、DLQ 分離「短暫失敗」與「永久毒訊息」。揭露 managed queue 在工作排程的取捨：trade ordering 換 scaling。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / DLQ 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/dynein-building-a-distributed-delayed-job-queueing-system-93ab10f05f99">Dynein: Building a Distributed Delayed Job Queueing System&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS at-least-once + DLQ 模型在工作排程的對齊邏輯。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 構建 Dynein 分散式延遲任務排程系統取代 Resque（受限於單 Redis 實例）。明確選 SQS、利用 at-least-once delivery、dead letter queue、individual message acknowledgment、access control 與 encryption-at-rest。每個 scheduler instance 達 ~1000 QPS、可水平擴展。</p>
<h2 id="判讀">判讀</h2>
<p>at-least-once 對工作排程「不丟資料」假設足夠、SQS wrap DynamoDB 處理 &gt; 15 分鐘 delay、DLQ 分離「短暫失敗」與「永久毒訊息」。揭露 managed queue 在工作排程的取捨：trade ordering 換 scaling。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / DLQ 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/dynein-building-a-distributed-delayed-job-queueing-system-93ab10f05f99">Dynein: Building a Distributed Delayed Job Queueing System</a></li>
</ul>
]]></content:encoded></item><item><title>3.C49 Airbnb Inspekt：Visibility timeout 當 retry budget</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/</guid><description>&lt;p>這個案例的核心責任是說明 visibility timeout 不只是「處理時間」、可當隱式的 retry 機制。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 的 Inspekt 隱私資料掃描系統用 SQS task queue 派發 scan task（每 table/object/app 一個 message）、Scanner nodes 水平 pull。&amp;ldquo;each message reappears N times back into the queue until a scanner node deletes it&amp;rdquo; 是 visibility timeout 在實戰的應用。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 message 重現次數做 retry budget、scanner 失敗時不用自管 retry table。揭露 SQS 的「不刪除即重現」是設計、不是 bug、可以當隱式 retry 機制用。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Visibility timeout + in-flight messages。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/automating-data-protection-at-scale-part-2-c2b8d2068216">Automating Data Protection at Scale Part 2&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 visibility timeout 不只是「處理時間」、可當隱式的 retry 機制。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 的 Inspekt 隱私資料掃描系統用 SQS task queue 派發 scan task（每 table/object/app 一個 message）、Scanner nodes 水平 pull。&ldquo;each message reappears N times back into the queue until a scanner node deletes it&rdquo; 是 visibility timeout 在實戰的應用。</p>
<h2 id="判讀">判讀</h2>
<p>用 message 重現次數做 retry budget、scanner 失敗時不用自管 retry table。揭露 SQS 的「不刪除即重現」是設計、不是 bug、可以當隱式 retry 機制用。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Visibility timeout + in-flight messages。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/automating-data-protection-at-scale-part-2-c2b8d2068216">Automating Data Protection at Scale Part 2</a></li>
</ul>
]]></content:encoded></item><item><title>3.C50 Capital One：Visibility timeout 設計與 Lambda event source</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/</guid><description>&lt;p>Capital One 的 SQS + Lambda 實務揭露了 visibility timeout 的雙邊風險 — 太短導致重複處理、太長延遲 retry — 以及 Lambda event source mapping 的 scaling 行為跟直覺不同的地方。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Capital One 是美國大型金融機構，tech blog 公開分享了 SQS + Lambda 的 event-driven 架構實踐。金融場景的 message 處理對正確性要求極高 — 重複處理一筆交易跟遺失一筆交易的代價都是具體的金錢損失。&lt;/p>
&lt;p>SQS 是 AWS 原生的 managed queue，Lambda 是 serverless compute。兩者搭配的 event source mapping 是 AWS 上最常見的 event-driven 入門架構 — 看起來簡單（SQS → Lambda 自動觸發），但 visibility timeout 跟 Lambda scaling 的互動有不少實務細節。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="visibility-timeout-的雙邊風險">Visibility timeout 的雙邊風險&lt;/h3>
&lt;p>SQS 的 visibility timeout 定義了「consumer 取走訊息後，其他 consumer 多久之後才能再看到這筆訊息」。它是 SQS 的核心容錯機制 — consumer 處理失敗（crash、timeout）時，visibility timeout 到期後訊息重新出現在 queue 裡，讓其他 consumer 接手。&lt;/p>
&lt;p>&lt;strong>Timeout 太短&lt;/strong>：consumer 還在處理中、visibility timeout 已到期、另一個 consumer 取走同一筆訊息開始處理 — 重複處理。金融場景的重複處理可能導致重複扣款或重複退款。&lt;/p>
&lt;p>&lt;strong>Timeout 太長&lt;/strong>：consumer 處理失敗、需要等很久 visibility timeout 才到期、訊息才重新出現 — retry 延遲。原本幾秒就能被其他 consumer 接手的訊息，要等 15 分鐘才 retry。&lt;/p>
&lt;p>Capital One 的實務建議是 visibility timeout 設為「最大預期處理時間 + 少量緩衝」。例如：最大處理時間 30 秒 → visibility timeout 設 45 秒。&lt;/p>
&lt;h3 id="lambda-event-source-mapping-的-scaling-行為">Lambda event source mapping 的 scaling 行為&lt;/h3>
&lt;p>Lambda 跟 SQS 的整合透過 event source mapping — Lambda 服務自動從 SQS long polling 取訊息、觸發 Lambda function。使用者不需要自己寫 polling 邏輯。&lt;/p>
&lt;p>Capital One 揭露的 scaling 行為跟「Lambda 自動擴展」的直覺不同：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>初始狀態&lt;/strong>：Lambda 啟動 5 個 long polling connection（poller）&lt;/li>
&lt;li>&lt;strong>Scale up&lt;/strong>：每分鐘最多新增 60 個 poller instance（每個 instance 處理一批 message）&lt;/li>
&lt;li>&lt;strong>上限&lt;/strong>：最多 1000 個並行 batch&lt;/li>
&lt;/ul>
&lt;p>這意味著突發流量（queue 瞬間湧入大量訊息）的消化速度不是即時的 — Lambda 需要數分鐘才能 scale 到足夠的並行度。在這段 ramp-up 期間，queue depth 會持續增長。&lt;/p>
&lt;h3 id="batch-size-跟-visibility-timeout-的互動">Batch size 跟 visibility timeout 的互動&lt;/h3>
&lt;p>Lambda event source mapping 預設 batch size = 10 — 一次取 10 筆訊息、用一個 Lambda invocation 處理。如果 batch 中的某一筆處理特別慢，整個 batch 的處理時間會被拉長。&lt;/p></description><content:encoded><![CDATA[<p>Capital One 的 SQS + Lambda 實務揭露了 visibility timeout 的雙邊風險 — 太短導致重複處理、太長延遲 retry — 以及 Lambda event source mapping 的 scaling 行為跟直覺不同的地方。</p>
<h2 id="業務背景">業務背景</h2>
<p>Capital One 是美國大型金融機構，tech blog 公開分享了 SQS + Lambda 的 event-driven 架構實踐。金融場景的 message 處理對正確性要求極高 — 重複處理一筆交易跟遺失一筆交易的代價都是具體的金錢損失。</p>
<p>SQS 是 AWS 原生的 managed queue，Lambda 是 serverless compute。兩者搭配的 event source mapping 是 AWS 上最常見的 event-driven 入門架構 — 看起來簡單（SQS → Lambda 自動觸發），但 visibility timeout 跟 Lambda scaling 的互動有不少實務細節。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="visibility-timeout-的雙邊風險">Visibility timeout 的雙邊風險</h3>
<p>SQS 的 visibility timeout 定義了「consumer 取走訊息後，其他 consumer 多久之後才能再看到這筆訊息」。它是 SQS 的核心容錯機制 — consumer 處理失敗（crash、timeout）時，visibility timeout 到期後訊息重新出現在 queue 裡，讓其他 consumer 接手。</p>
<p><strong>Timeout 太短</strong>：consumer 還在處理中、visibility timeout 已到期、另一個 consumer 取走同一筆訊息開始處理 — 重複處理。金融場景的重複處理可能導致重複扣款或重複退款。</p>
<p><strong>Timeout 太長</strong>：consumer 處理失敗、需要等很久 visibility timeout 才到期、訊息才重新出現 — retry 延遲。原本幾秒就能被其他 consumer 接手的訊息，要等 15 分鐘才 retry。</p>
<p>Capital One 的實務建議是 visibility timeout 設為「最大預期處理時間 + 少量緩衝」。例如：最大處理時間 30 秒 → visibility timeout 設 45 秒。</p>
<h3 id="lambda-event-source-mapping-的-scaling-行為">Lambda event source mapping 的 scaling 行為</h3>
<p>Lambda 跟 SQS 的整合透過 event source mapping — Lambda 服務自動從 SQS long polling 取訊息、觸發 Lambda function。使用者不需要自己寫 polling 邏輯。</p>
<p>Capital One 揭露的 scaling 行為跟「Lambda 自動擴展」的直覺不同：</p>
<ul>
<li><strong>初始狀態</strong>：Lambda 啟動 5 個 long polling connection（poller）</li>
<li><strong>Scale up</strong>：每分鐘最多新增 60 個 poller instance（每個 instance 處理一批 message）</li>
<li><strong>上限</strong>：最多 1000 個並行 batch</li>
</ul>
<p>這意味著突發流量（queue 瞬間湧入大量訊息）的消化速度不是即時的 — Lambda 需要數分鐘才能 scale 到足夠的並行度。在這段 ramp-up 期間，queue depth 會持續增長。</p>
<h3 id="batch-size-跟-visibility-timeout-的互動">Batch size 跟 visibility timeout 的互動</h3>
<p>Lambda event source mapping 預設 batch size = 10 — 一次取 10 筆訊息、用一個 Lambda invocation 處理。如果 batch 中的某一筆處理特別慢，整個 batch 的處理時間會被拉長。</p>
<p>Visibility timeout 要覆蓋整個 batch 的處理時間（包含最慢的那一筆），否則 batch 還在處理中、早期取走的訊息 visibility timeout 到期、被其他 poller 重新取走 — 導致重複處理。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>設計參數</th>
          <th>建議值</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Visibility timeout</td>
          <td>最大處理時間 + 緩衝（例 45 秒）</td>
          <td>太短重複、太長延遲 retry</td>
      </tr>
      <tr>
          <td>Batch size</td>
          <td>依處理時間變異度調整</td>
          <td>Batch 大省 invocation 費用、但延長 visibility 需求</td>
      </tr>
      <tr>
          <td>DLQ</td>
          <td>設定 maxReceiveCount（例 3 次）</td>
          <td>避免 poison message 無限 retry</td>
      </tr>
      <tr>
          <td>Concurrency limit</td>
          <td>依下游承受能力設定</td>
          <td>避免 Lambda 爆量壓垮下游 DB</td>
      </tr>
  </tbody>
</table>
<h3 id="idempotency-作為安全網">Idempotency 作為安全網</h3>
<p>Visibility timeout 無法完全避免重複處理（網路分區、Lambda timeout 等邊界條件）。Capital One 的做法是在 Lambda function 內實作 <a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> — 用 message ID 做去重，確保同一筆訊息被多次處理時結果相同。</p>
<p>Idempotency 把 visibility timeout 的精確度要求降低 — 即使偶爾重複處理，業務結果仍然正確。Visibility timeout 仍然需要合理設定（降低不必要的重複 invocation 成本），但 idempotency 是「即使設錯也不會造成業務錯誤」的安全網。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a>：visibility timeout、in-flight limit、Lambda event source 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：at-least-once 語意下的 consumer 端 idempotency</li>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：visibility timeout 是 SQS 的 delivery guarantee 機制</li>
<li><a href="/blog/backend/03-message-queue/queue-consumer-retry-replay-handoff/" data-link-title="3.8 Queue Consumer Retry 與 Replay Handoff（實作示範）" data-link-desc="以 order_created consumer 示範 queue 路徑如何交付 idempotency evidence、DLQ handling、replay runbook 與 incident decision log。">3.8 queue consumer retry replay handoff</a>：DLQ + maxReceiveCount 的 retry 升級策略</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>SQS + Lambda 架構中出現訊息重複處理（CloudWatch 的 <code>ApproximateNumberOfMessagesNotVisible</code> 跟 <code>NumberOfMessagesReceived</code> 比例異常）</li>
<li>Lambda function 的 timeout 跟 SQS visibility timeout 的關係沒有明確設計</li>
<li>突發流量時 queue depth 持續增長、Lambda 的 concurrent execution 沒有立刻跟上</li>
<li>Batch processing 中的慢訊息拖慢整個 batch、造成 visibility timeout 到期</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.capitalone.com/tech/cloud/using-aws-solutions-for-event-driven-serverless-architectures/">Using AWS Solutions for Event-Driven Serverless Architectures</a></li>
</ul>
]]></content:encoded></item><item><title>3.C51 Atlassian JiRT：Kinesis + SQS subscription</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/</guid><description>&lt;p>這個案例的核心責任是說明 SQS 作為 streaming source 的 per-consumer subscription 模式。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Atlassian 內部 event bus StreamHub 底層用 Kinesis、但「每個 consumer 自己準備 SQS queue 接收 event」。JiRT 即時服務透過此模式把輪詢式（~1 min）改成 event-driven（秒級）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>在 Kinesis 上面疊 SQS 讓 consumer 各自設定 retention、各自獨立 visibility timeout。揭露「stream + per-consumer queue」是 fan-out 場景的常見複合 pattern、不是 streaming vs queue 二選一。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / SQS 作為 fan-out subscriber。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（streaming + queue 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/using-an-event-driven-architecture-to-improve-jira-software-responsiveness">Using an Event-Driven Architecture to Improve Jira Software Responsiveness&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS 作為 streaming source 的 per-consumer subscription 模式。</p>
<h2 id="觀察">觀察</h2>
<p>Atlassian 內部 event bus StreamHub 底層用 Kinesis、但「每個 consumer 自己準備 SQS queue 接收 event」。JiRT 即時服務透過此模式把輪詢式（~1 min）改成 event-driven（秒級）。</p>
<h2 id="判讀">判讀</h2>
<p>在 Kinesis 上面疊 SQS 讓 consumer 各自設定 retention、各自獨立 visibility timeout。揭露「stream + per-consumer queue」是 fan-out 場景的常見複合 pattern、不是 streaming vs queue 二選一。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / SQS 作為 fan-out subscriber。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（streaming + queue 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/using-an-event-driven-architecture-to-improve-jira-software-responsiveness">Using an Event-Driven Architecture to Improve Jira Software Responsiveness</a></li>
</ul>
]]></content:encoded></item><item><title>Heroku：Routing 控制事件與多租戶影響</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/</guid><description>&lt;p>這起案例的核心責任是守住路由層故障的擴散邊界。PaaS 共享入口若失效，租戶影響會快速放大。&lt;/p>
&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>router error spike&lt;/td>
 &lt;td>入口故障是否擴散&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>tenant-level impact variance&lt;/td>
 &lt;td>影響是否呈現分區差異&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>status lag&lt;/td>
 &lt;td>對外更新是否落後&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「路由層共享入口」對多租戶的擴散影響。主要風險是未先切租戶影響就全量回復，導致二次壅塞。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>事故流程需先切分租戶影響，再做回復批次，並回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是守住路由層故障的擴散邊界。PaaS 共享入口若失效，租戶影響會快速放大。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>router error spike</td>
          <td>入口故障是否擴散</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
      <tr>
          <td>tenant-level impact variance</td>
          <td>影響是否呈現分區差異</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
      <tr>
          <td>status lag</td>
          <td>對外更新是否落後</td>
          <td><a href="/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「路由層共享入口」對多租戶的擴散影響。主要風險是未先切租戶影響就全量回復，導致二次壅塞。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>事故流程需先切分租戶影響，再做回復批次，並回寫 <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4</a> 與 <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a>。</p>
]]></content:encoded></item><item><title>Microsoft：變更治理與可靠性門檻</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/</guid><description>&lt;p>Microsoft 案例的核心責任是把變更管理制度化。對大型 SaaS 而言，事故常由多個低風險變更疊加而成，治理重點在於發布節奏與風險分層。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>高頻變更環境中，單一變更看起來都可接受，但累積後會突破可靠性預算。若缺少一致 gate，團隊難以提早收斂。&lt;/p>
&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;/tbody>
&lt;/table>
&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>release rollback frequency&lt;/td>
 &lt;td>變更品質是否退化&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>freeze trigger count&lt;/td>
 &lt;td>凍結是否過晚&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>incident recurrence&lt;/td>
 &lt;td>同型事件是否重複&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/repeated-incident-toil/" data-link-title="8.13 Repeated Incident 與 Toil 治理" data-link-desc="把同型事故反覆發生與重複手動修復作為工程化治理對象">8.13&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>把風險分層寫進 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19&lt;/a>，並將復盤項目回寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Microsoft 案例的核心責任是把變更管理制度化。對大型 SaaS 而言，事故常由多個低風險變更疊加而成，治理重點在於發布節奏與風險分層。</p>
<h2 id="問題場景">問題場景</h2>
<p>高頻變更環境中，單一變更看起來都可接受，但累積後會突破可靠性預算。若缺少一致 gate，團隊難以提早收斂。</p>
<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>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>release rollback frequency</td>
          <td>變更品質是否退化</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>freeze trigger count</td>
          <td>凍結是否過晚</td>
          <td><a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6</a></td>
      </tr>
      <tr>
          <td>incident recurrence</td>
          <td>同型事件是否重複</td>
          <td><a href="/blog/backend/08-incident-response/repeated-incident-toil/" data-link-title="8.13 Repeated Incident 與 Toil 治理" data-link-desc="把同型事故反覆發生與重複手動修復作為工程化治理對象">8.13</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>把風險分層寫進 <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19</a>，並將復盤項目回寫 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a>。</p>
]]></content:encoded></item><item><title>Shopify：BFCM 容量治理與 Game Day 驗證節奏</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/</guid><description>&lt;p>Shopify 案例的核心責任是把可預期峰值轉成可預演流程。當流量高峰是年度固定事件，可靠性工作重點是提前把容量與失效路徑變成可驗證資產，臨場救火代表準備不足。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>BFCM 類型高峰會同時放大三種壓力：流量突增、資料層寫入壓力、跨服務依賴抖動。若只在活動前做單次壓測，團隊通常只能看到系統上限，無法看到恢復節奏與指揮負載。&lt;/p>
&lt;p>Shopify 的做法是把容量規劃、隔離邊界與演練節奏綁成同一條年度路線。&lt;/p>
&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>Capacity planning baseline&lt;/td>
 &lt;td>高峰前可承受上限是多少&lt;/td>
 &lt;td>容量預算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod/isolation boundary&lt;/td>
 &lt;td>故障影響如何限制在局部&lt;/td>
 &lt;td>擴散邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Game Day&lt;/td>
 &lt;td>高峰前如何驗證假設&lt;/td>
 &lt;td>演練證據&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resiliency matrix&lt;/td>
 &lt;td>服務與失效模式如何對齊&lt;/td>
 &lt;td>控制面清單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個機制的價值是讓高峰風險在活動前被分批消化，而不是在活動中一次承擔。&lt;/p>
&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>peak-load headroom&lt;/td>
 &lt;td>高峰前安全緩衝是否充足&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>game-day action closure&lt;/td>
 &lt;td>演練缺口是否完成回寫&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>pod-level degradation&lt;/td>
 &lt;td>退化是否被限制在局部&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>command handoff latency&lt;/td>
 &lt;td>高峰日交接節奏是否穩定&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&amp;#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>把高峰準備當成一次性專案會讓知識斷層快速累積。可靠做法是把每輪活動輸出的缺口回寫成固定資產：runbook、matrix、驗證腳本與放行門檻。這讓下一輪準備從更高基準開始，而不是重來。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>若要落地本案例，先從 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a> 建容量模型，再在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a> 定義高峰穩態。演練證據回寫 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Shopify 案例的核心責任是把可預期峰值轉成可預演流程。當流量高峰是年度固定事件，可靠性工作重點是提前把容量與失效路徑變成可驗證資產，臨場救火代表準備不足。</p>
<h2 id="問題場景">問題場景</h2>
<p>BFCM 類型高峰會同時放大三種壓力：流量突增、資料層寫入壓力、跨服務依賴抖動。若只在活動前做單次壓測，團隊通常只能看到系統上限，無法看到恢復節奏與指揮負載。</p>
<p>Shopify 的做法是把容量規劃、隔離邊界與演練節奏綁成同一條年度路線。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Capacity planning baseline</td>
          <td>高峰前可承受上限是多少</td>
          <td>容量預算</td>
      </tr>
      <tr>
          <td>Pod/isolation boundary</td>
          <td>故障影響如何限制在局部</td>
          <td>擴散邊界</td>
      </tr>
      <tr>
          <td>Game Day</td>
          <td>高峰前如何驗證假設</td>
          <td>演練證據</td>
      </tr>
      <tr>
          <td>Resiliency matrix</td>
          <td>服務與失效模式如何對齊</td>
          <td>控制面清單</td>
      </tr>
  </tbody>
</table>
<p>這個機制的價值是讓高峰風險在活動前被分批消化，而不是在活動中一次承擔。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>peak-load headroom</td>
          <td>高峰前安全緩衝是否充足</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td>game-day action closure</td>
          <td>演練缺口是否完成回寫</td>
          <td><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a></td>
      </tr>
      <tr>
          <td>pod-level degradation</td>
          <td>退化是否被限制在局部</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>command handoff latency</td>
          <td>高峰日交接節奏是否穩定</td>
          <td><a href="/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>把高峰準備當成一次性專案會讓知識斷層快速累積。可靠做法是把每輪活動輸出的缺口回寫成固定資產：runbook、matrix、驗證腳本與放行門檻。這讓下一輪準備從更高基準開始，而不是重來。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>若要落地本案例，先從 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a> 建容量模型，再在 <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a> 定義高峰穩態。演練證據回寫 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a> 與 <a href="/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6</a>。</p>
]]></content:encoded></item><item><title>Microsoft：Safe Deployment Practices 與 Resilience Patterns</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/</guid><description>&lt;p>Safe deployment practices 的核心責任是讓大規模服務的每次變更都經過漸進驗證。ring-based deployment 把影響面從小到大排列，每一層是下一層的安全網。resilience patterns 的責任是讓服務在依賴失效時有標準化的降級行為，降低臨場判斷的成本。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>Azure 與 M365 等大型 SaaS 每天部署數千次變更，單靠人工審核不可擴展。當部署速度超過人工審查能力，需要一套自動化的漸進驗證流程來控制每次變更的風險。同時，服務間的依賴關係複雜，任何一個依賴的劣化都可能影響多個下游服務，需要標準化的降級行為避免連鎖失效。&lt;/p>
&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>Ring-based deployment&lt;/td>
 &lt;td>變更如何從小範圍漸進到全量&lt;/td>
 &lt;td>分層放行節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Automatic rollback&lt;/td>
 &lt;td>health signal 異常時如何自動退回&lt;/td>
 &lt;td>自動化回退條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resilience patterns&lt;/td>
 &lt;td>依賴失效時服務如何標準化降級&lt;/td>
 &lt;td>retry / breaker / bulkhead&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Blast radius control&lt;/td>
 &lt;td>ring boundary 如何限制影響範圍&lt;/td>
 &lt;td>每層的最大影響面&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Ring-based deployment 的標準路徑是 Ring 0（internal dogfood）→ Ring 1（canary）→ Ring 2（early adopters）→ Ring 3（broad）。每一層的 go/no-go 條件包含 health signal delta（跟前一版 baseline 比較）、error rate、latency percentile 與 customer impact signal。只有當前層的所有指標都在可接受範圍內，才進入下一層。&lt;/p>
&lt;p>Automatic rollback 是 ring progression 的安全網。當 health signal 超過預設門檻時，系統自動回退到前一版，不需要等人工判斷。自動回退的觸發條件要嚴格定義 — 過於敏感會造成頻繁 false positive rollback，過於寬鬆會讓問題擴散到下一個 ring。&lt;/p>
&lt;p>Resilience patterns 讓依賴失效時的行為可預測。retry with &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter&lt;/a> 避免重試風暴、circuit breaker 在依賴持續失效時停止發送請求、bulkhead isolation 把不同依賴的資源池隔開。這些 patterns 的價值在於標準化 — 團隊不需要每次都從頭設計降級邏輯，而是從已驗證的 pattern 庫中選擇。&lt;/p>
&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>ring health delta&lt;/td>
 &lt;td>每層的品質是否維持&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>automatic rollback frequency&lt;/td>
 &lt;td>自動回退是否過於頻繁或過少&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>circuit breaker trip rate&lt;/td>
 &lt;td>依賴失效是否被及時隔離&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>deployment velocity&lt;/td>
 &lt;td>漸進部署是否拖慢交付速度&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>Ring progression 的觀察窗長度需要跟服務的 feedback loop 對齊。通用服務可能幾分鐘內就能看到異常，但有延遲確認的服務（結算、對帳、非同步補償）可能需要數小時甚至數天才暴露問題。觀察窗太短會漏掉延遲暴露的問題；太長會拖慢所有變更的交付速度。分服務類型設定不同觀察窗，比用統一時長更有效。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先把 ring-based deployment 的 go/no-go 條件寫進 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate&lt;/a>，再把 resilience patterns 的 circuit breaker 與 retry 設計接到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 Dependency Reliability Budget&lt;/a>。deployment velocity 的量測回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 Reliability Metrics&lt;/a>，CI 整合回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI Pipeline&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Safe deployment practices 的核心責任是讓大規模服務的每次變更都經過漸進驗證。ring-based deployment 把影響面從小到大排列，每一層是下一層的安全網。resilience patterns 的責任是讓服務在依賴失效時有標準化的降級行為，降低臨場判斷的成本。</p>
<h2 id="問題場景">問題場景</h2>
<p>Azure 與 M365 等大型 SaaS 每天部署數千次變更，單靠人工審核不可擴展。當部署速度超過人工審查能力，需要一套自動化的漸進驗證流程來控制每次變更的風險。同時，服務間的依賴關係複雜，任何一個依賴的劣化都可能影響多個下游服務，需要標準化的降級行為避免連鎖失效。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ring-based deployment</td>
          <td>變更如何從小範圍漸進到全量</td>
          <td>分層放行節奏</td>
      </tr>
      <tr>
          <td>Automatic rollback</td>
          <td>health signal 異常時如何自動退回</td>
          <td>自動化回退條件</td>
      </tr>
      <tr>
          <td>Resilience patterns</td>
          <td>依賴失效時服務如何標準化降級</td>
          <td>retry / breaker / bulkhead</td>
      </tr>
      <tr>
          <td>Blast radius control</td>
          <td>ring boundary 如何限制影響範圍</td>
          <td>每層的最大影響面</td>
      </tr>
  </tbody>
</table>
<p>Ring-based deployment 的標準路徑是 Ring 0（internal dogfood）→ Ring 1（canary）→ Ring 2（early adopters）→ Ring 3（broad）。每一層的 go/no-go 條件包含 health signal delta（跟前一版 baseline 比較）、error rate、latency percentile 與 customer impact signal。只有當前層的所有指標都在可接受範圍內，才進入下一層。</p>
<p>Automatic rollback 是 ring progression 的安全網。當 health signal 超過預設門檻時，系統自動回退到前一版，不需要等人工判斷。自動回退的觸發條件要嚴格定義 — 過於敏感會造成頻繁 false positive rollback，過於寬鬆會讓問題擴散到下一個 ring。</p>
<p>Resilience patterns 讓依賴失效時的行為可預測。retry with <a href="/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter</a> 避免重試風暴、circuit breaker 在依賴持續失效時停止發送請求、bulkhead isolation 把不同依賴的資源池隔開。這些 patterns 的價值在於標準化 — 團隊不需要每次都從頭設計降級邏輯，而是從已驗證的 pattern 庫中選擇。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ring health delta</td>
          <td>每層的品質是否維持</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a></td>
      </tr>
      <tr>
          <td>automatic rollback frequency</td>
          <td>自動回退是否過於頻繁或過少</td>
          <td><a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18</a></td>
      </tr>
      <tr>
          <td>circuit breaker trip rate</td>
          <td>依賴失效是否被及時隔離</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>deployment velocity</td>
          <td>漸進部署是否拖慢交付速度</td>
          <td><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>Ring progression 的觀察窗長度需要跟服務的 feedback loop 對齊。通用服務可能幾分鐘內就能看到異常，但有延遲確認的服務（結算、對帳、非同步補償）可能需要數小時甚至數天才暴露問題。觀察窗太短會漏掉延遲暴露的問題；太長會拖慢所有變更的交付速度。分服務類型設定不同觀察窗，比用統一時長更有效。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先把 ring-based deployment 的 go/no-go 條件寫進 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a>，再把 resilience patterns 的 circuit breaker 與 retry 設計接到 <a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 Dependency Reliability Budget</a>。deployment velocity 的量測回到 <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 Reliability Metrics</a>，CI 整合回到 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI Pipeline</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/devops/operate/safe-deployment-practices">Safe deployment practices</a></li>
<li><a href="https://learn.microsoft.com/en-gb/azure/well-architected/reliability/design-patterns">Architecture design patterns that support reliability</a></li>
</ul>
]]></content:encoded></item><item><title>Shopify：Pod Architecture 與 Resiliency Matrix</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/</guid><description>&lt;p>Shopify pod architecture 的核心責任是把多租戶流量限制在獨立的 pod 內，讓一個 pod 的故障不影響其他 pod 的商店。&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/resiliency-matrix/" data-link-title="Resiliency Matrix" data-link-desc="服務與失敗模式的交叉矩陣，標記每個交叉點的防護狀態與驗證覆蓋">resiliency matrix&lt;/a> 的核心責任是把每個服務的失敗模式與防護狀態列成可檢查的矩陣，讓 game day 有結構化的驗證清單。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>多租戶電商平台的流量分佈高度不均。大商店的促銷活動可能在短時間內吃掉共享資源的大部分容量，若缺少隔離機制，一個商店的流量爆增會拖垮同一基礎設施上的其他商店。&lt;/p>
&lt;p>隔離解決的是擴散問題，但隔離本身不回答「哪些失敗模式已經有防護、哪些還是缺口」。resiliency matrix 把這個問題結構化：每個服務列出已知的失敗模式，每種模式標註防護狀態，缺口直接成為下一輪演練的輸入。&lt;/p>
&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>Pod boundary&lt;/td>
 &lt;td>一個商店的故障最多影響到哪裡&lt;/td>
 &lt;td>獨立隔離單位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Tenant routing&lt;/td>
 &lt;td>商店按什麼規則分配到 pod&lt;/td>
 &lt;td>映射策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resiliency matrix&lt;/td>
 &lt;td>每個服務的失敗模式是否都有對應防護&lt;/td>
 &lt;td>防護覆蓋狀態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Game Day 整合&lt;/td>
 &lt;td>matrix 的缺口如何轉成演練題目&lt;/td>
 &lt;td>演練驗證清單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Pod boundary 的設計是每個 pod 擁有獨立的 DB、cache 與 compute 資源。這讓 pod 之間在資源層完全隔離 — 一個 pod 的 DB 連線耗盡不會影響其他 pod 的查詢能力。隔離的代價是資源利用率降低，但在峰值場景下，隔離帶來的故障局部化價值遠高於利用率損失。&lt;/p>
&lt;p>Tenant routing 決定商店到 pod 的映射。映射規則通常考慮商店規模（大商店獨立 pod 或少量共用）、地理區域、與風險等級（新商店 vs 穩定商店）。映射一旦建立，變更需要 migration — 這是隔離架構的操作成本之一。&lt;/p>
&lt;p>Resiliency matrix 是 service × failure mode 的二維矩陣。每格填入三種狀態之一：covered（有防護且已驗證）、gap（已知缺口、尚未補齊）、in-progress（正在建設）。matrix 的維護責任跟服務 owner 綁定，每輪 game day 前 review 一次。&lt;/p>
&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>pod-level error isolation&lt;/td>
 &lt;td>故障是否被限制在單一 pod 內&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>matrix gap count trend&lt;/td>
 &lt;td>缺口是否在收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cross-pod contamination&lt;/td>
 &lt;td>是否有故障穿越 pod 邊界&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>game-day action closure&lt;/td>
 &lt;td>演練暴露的缺口是否被關閉&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/failure-mode-pre-mortem/" data-link-title="6.5 失敗模式預判（Pre-mortem 與 FMEA）" data-link-desc="用 pre-mortem 反向推導失敗路徑、用 FMEA 分類軸評估驗證缺口，把可靠性盲區變成可排序的改善輸入">6.5&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見陷阱">常見陷阱&lt;/h2>
&lt;p>resiliency matrix 最大的風險是退化為文件。若 matrix 只在年度 review 更新一次、gap 沒有 owner、action item 沒有 deadline，它就失去了驅動演練的功能。有效的 matrix 跟 game day 節奏綁定：每輪演練前 review gap、演練後更新狀態、新服務上線時補齊對應行列。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/failure-mode-pre-mortem/" data-link-title="6.5 失敗模式預判（Pre-mortem 與 FMEA）" data-link-desc="用 pre-mortem 反向推導失敗路徑、用 FMEA 分類軸評估驗證缺口，把可靠性盲區變成可排序的改善輸入">6.5 失敗模式預判&lt;/a>：resiliency matrix 是 FMEA 的落地工具&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency budget&lt;/a>：pod 隔離是依賴預算的實作手段&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety&lt;/a>：跨 pod 實驗的 blast radius 控制&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt&lt;/a>：matrix gap 回寫成 reliability debt&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://shopify.engineering/four-steps-creating-effective-game-day-tests">Four Steps to Creating Effective Game Day Tests&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://shopify.engineering/resiliency-planning-for-high-traffic-events">Resiliency Planning for High-Traffic Events&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://shopify.engineering/a-pods-architecture-to-allow-shopify-to-scale">A Pods Architecture To Allow Shopify To Scale&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Shopify pod architecture 的核心責任是把多租戶流量限制在獨立的 pod 內，讓一個 pod 的故障不影響其他 pod 的商店。<a href="/blog/backend/knowledge-cards/resiliency-matrix/" data-link-title="Resiliency Matrix" data-link-desc="服務與失敗模式的交叉矩陣，標記每個交叉點的防護狀態與驗證覆蓋">resiliency matrix</a> 的核心責任是把每個服務的失敗模式與防護狀態列成可檢查的矩陣，讓 game day 有結構化的驗證清單。</p>
<h2 id="問題場景">問題場景</h2>
<p>多租戶電商平台的流量分佈高度不均。大商店的促銷活動可能在短時間內吃掉共享資源的大部分容量，若缺少隔離機制，一個商店的流量爆增會拖垮同一基礎設施上的其他商店。</p>
<p>隔離解決的是擴散問題，但隔離本身不回答「哪些失敗模式已經有防護、哪些還是缺口」。resiliency matrix 把這個問題結構化：每個服務列出已知的失敗模式，每種模式標註防護狀態，缺口直接成為下一輪演練的輸入。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pod boundary</td>
          <td>一個商店的故障最多影響到哪裡</td>
          <td>獨立隔離單位</td>
      </tr>
      <tr>
          <td>Tenant routing</td>
          <td>商店按什麼規則分配到 pod</td>
          <td>映射策略</td>
      </tr>
      <tr>
          <td>Resiliency matrix</td>
          <td>每個服務的失敗模式是否都有對應防護</td>
          <td>防護覆蓋狀態</td>
      </tr>
      <tr>
          <td>Game Day 整合</td>
          <td>matrix 的缺口如何轉成演練題目</td>
          <td>演練驗證清單</td>
      </tr>
  </tbody>
</table>
<p>Pod boundary 的設計是每個 pod 擁有獨立的 DB、cache 與 compute 資源。這讓 pod 之間在資源層完全隔離 — 一個 pod 的 DB 連線耗盡不會影響其他 pod 的查詢能力。隔離的代價是資源利用率降低，但在峰值場景下，隔離帶來的故障局部化價值遠高於利用率損失。</p>
<p>Tenant routing 決定商店到 pod 的映射。映射規則通常考慮商店規模（大商店獨立 pod 或少量共用）、地理區域、與風險等級（新商店 vs 穩定商店）。映射一旦建立，變更需要 migration — 這是隔離架構的操作成本之一。</p>
<p>Resiliency matrix 是 service × failure mode 的二維矩陣。每格填入三種狀態之一：covered（有防護且已驗證）、gap（已知缺口、尚未補齊）、in-progress（正在建設）。matrix 的維護責任跟服務 owner 綁定，每輪 game day 前 review 一次。</p>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>pod-level error isolation</td>
          <td>故障是否被限制在單一 pod 內</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>matrix gap count trend</td>
          <td>缺口是否在收斂</td>
          <td><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a></td>
      </tr>
      <tr>
          <td>cross-pod contamination</td>
          <td>是否有故障穿越 pod 邊界</td>
          <td><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a></td>
      </tr>
      <tr>
          <td>game-day action closure</td>
          <td>演練暴露的缺口是否被關閉</td>
          <td><a href="/blog/backend/06-reliability/failure-mode-pre-mortem/" data-link-title="6.5 失敗模式預判（Pre-mortem 與 FMEA）" data-link-desc="用 pre-mortem 反向推導失敗路徑、用 FMEA 分類軸評估驗證缺口，把可靠性盲區變成可排序的改善輸入">6.5</a></td>
      </tr>
  </tbody>
</table>
<h2 id="常見陷阱">常見陷阱</h2>
<p>resiliency matrix 最大的風險是退化為文件。若 matrix 只在年度 review 更新一次、gap 沒有 owner、action item 沒有 deadline，它就失去了驅動演練的功能。有效的 matrix 跟 game day 節奏綁定：每輪演練前 review gap、演練後更新狀態、新服務上線時補齊對應行列。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/failure-mode-pre-mortem/" data-link-title="6.5 失敗模式預判（Pre-mortem 與 FMEA）" data-link-desc="用 pre-mortem 反向推導失敗路徑、用 FMEA 分類軸評估驗證缺口，把可靠性盲區變成可排序的改善輸入">6.5 失敗模式預判</a>：resiliency matrix 是 FMEA 的落地工具</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency budget</a>：pod 隔離是依賴預算的實作手段</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety</a>：跨 pod 實驗的 blast radius 控制</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt</a>：matrix gap 回寫成 reliability debt</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/four-steps-creating-effective-game-day-tests">Four Steps to Creating Effective Game Day Tests</a></li>
<li><a href="https://shopify.engineering/resiliency-planning-for-high-traffic-events">Resiliency Planning for High-Traffic Events</a></li>
<li><a href="https://shopify.engineering/a-pods-architecture-to-allow-shopify-to-scale">A Pods Architecture To Allow Shopify To Scale</a></li>
</ul>
]]></content:encoded></item><item><title>3.C52 Nielsen：Spark on EKS 雙 SQS 工作流</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/</guid><description>&lt;p>這個案例的核心責任是說明 SQS queue depth 作為 autoscale 訊號的真實案例。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Nielsen 每日處理 25 TB / 30 billion event。架構用兩個 SQS queue：work queue（待處理工作項）+ completion queue（回報完成）。Lambda 從 DB 拉檔案、組成 work item 推進 work queue、EKS pod 拉取處理、處理完寫 completion queue。基於 queue depth 自動擴 pod。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不用直接 Lambda invoke（pod 上跑長時間 Spark workload）、queue depth 當 backlog signal driving autoscale。揭露長 workload 場景該用 pod + queue depth、不是 Lambda function。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：CloudWatch metric + alarm / Standard queue / 長 workload autoscaling。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&amp;#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22 Trivago KEDA&lt;/a>（lag-based autoscale 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/architecture/how-nielsen-uses-serverless-concepts-on-amazon-eks-for-big-data-processing-with-spark-workloads/">How Nielsen Uses Serverless Concepts on Amazon EKS for Big Data Spark Workloads&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS queue depth 作為 autoscale 訊號的真實案例。</p>
<h2 id="觀察">觀察</h2>
<p>Nielsen 每日處理 25 TB / 30 billion event。架構用兩個 SQS queue：work queue（待處理工作項）+ completion queue（回報完成）。Lambda 從 DB 拉檔案、組成 work item 推進 work queue、EKS pod 拉取處理、處理完寫 completion queue。基於 queue depth 自動擴 pod。</p>
<h2 id="判讀">判讀</h2>
<p>不用直接 Lambda invoke（pod 上跑長時間 Spark workload）、queue depth 當 backlog signal driving autoscale。揭露長 workload 場景該用 pod + queue depth、不是 Lambda function。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：CloudWatch metric + alarm / Standard queue / 長 workload autoscaling。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22 Trivago KEDA</a>（lag-based autoscale 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/architecture/how-nielsen-uses-serverless-concepts-on-amazon-eks-for-big-data-processing-with-spark-workloads/">How Nielsen Uses Serverless Concepts on Amazon EKS for Big Data Spark Workloads</a></li>
</ul>
]]></content:encoded></item><item><title>3.C53 FINRA：S3 → SQS notification 大檔上傳</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/</guid><description>&lt;p>這個案例的核心責任是說明 S3 event notification 是 SQS 最經典 trigger、合規場景的 IAM 多層設定。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FINRA 金融監管機構、處理 broker-dealer 上傳大檔。Large File Service 用 S3 → SQS 通知模式：使用者上傳完 loading dock bucket、S3 推 SQS message 給 LFS、移檔後再推 &amp;ldquo;file available&amp;rdquo; SQS message 給下游。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>S3 通知是 SQS 最經典 trigger、KMS + bucket policy + queue 權限的合規場景（金融業要保留稽核軌跡）。揭露金融場景的 IAM 設計不是一道權限、是多層稽核軌跡。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：SQS + Lambda event source / IAM + Cross-account。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security 模組&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.finra.org/about/how-we-operate/technology/blog/large-file-service-securely-uploading-large-files-to-s3">FINRA Large File Service&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 S3 event notification 是 SQS 最經典 trigger、合規場景的 IAM 多層設定。</p>
<h2 id="觀察">觀察</h2>
<p>FINRA 金融監管機構、處理 broker-dealer 上傳大檔。Large File Service 用 S3 → SQS 通知模式：使用者上傳完 loading dock bucket、S3 推 SQS message 給 LFS、移檔後再推 &ldquo;file available&rdquo; SQS message 給下游。</p>
<h2 id="判讀">判讀</h2>
<p>S3 通知是 SQS 最經典 trigger、KMS + bucket policy + queue 權限的合規場景（金融業要保留稽核軌跡）。揭露金融場景的 IAM 設計不是一道權限、是多層稽核軌跡。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：SQS + Lambda event source / IAM + Cross-account。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security 模組</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.finra.org/about/how-we-operate/technology/blog/large-file-service-securely-uploading-large-files-to-s3">FINRA Large File Service</a></li>
</ul>
]]></content:encoded></item><item><title>3.C54 Twitch EventSub：SNS+SQS fan-out 給第三方</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/</guid><description>&lt;p>這個案例的核心責任是說明 SNS-SQS fan-out + dispatcher pattern 的實戰。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twitch 內部 Event Bus 發佈 ~1660 events/sec 到 SNS。EventSub（給第三方應用訂閱 Twitch 事件）用 SQS 接收 async notification、再由 Dispatcher fan-out 給各訂閱者。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>fan-out 後每個 consumer 要自己一個 queue。揭露 SNS → SQS 是 AWS 生態的 fan-out 標配、SQS 是第三方訂閱的 buffer 層、Dispatcher 是 application 級別的分發責任。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard queue + SQS + Lambda / SNS-SQS fan-out。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &amp;#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51 Atlassian JiRT&lt;/a>（subscription 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.twitch.tv/en/2023/09/28/twitch-state-of-engineering-2023/">Twitch State of Engineering 2023&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SNS-SQS fan-out + dispatcher pattern 的實戰。</p>
<h2 id="觀察">觀察</h2>
<p>Twitch 內部 Event Bus 發佈 ~1660 events/sec 到 SNS。EventSub（給第三方應用訂閱 Twitch 事件）用 SQS 接收 async notification、再由 Dispatcher fan-out 給各訂閱者。</p>
<h2 id="判讀">判讀</h2>
<p>fan-out 後每個 consumer 要自己一個 queue。揭露 SNS → SQS 是 AWS 生態的 fan-out 標配、SQS 是第三方訂閱的 buffer 層、Dispatcher 是 application 級別的分發責任。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard queue + SQS + Lambda / SNS-SQS fan-out。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51 Atlassian JiRT</a>（subscription 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.twitch.tv/en/2023/09/28/twitch-state-of-engineering-2023/">Twitch State of Engineering 2023</a></li>
</ul>
]]></content:encoded></item><item><title>3.C55 SmugMug：SQS 驅動可重放搜尋管線</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/</guid><description>&lt;p>這個案例的核心責任是說明 SQS 作為「workload generator」的分散式平行化角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>SmugMug 用 SQS 兩種模式：(1) backfill — script 推 DynamoDB scan-segment 指令進 SQS、Lambda 拉取做平行掃描寫 OpenSearch、(2) 鏡像查詢 — production query 推副本 SQS、Lambda replay 到 replica domain。每小時可 index &amp;gt; 1 billion document、不影響 production。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SQS 作為「workload generator」分散式平行化、不需協調 worker 數量。揭露 SQS 不只是「事件 queue」、也是「並行任務分散」的協調基礎。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard queue / Long polling / SQS + Lambda event source。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/smugmugs-durable-search-pipelines-for-amazon-opensearch-service/">SmugMug&amp;rsquo;s Durable Search Pipelines for Amazon OpenSearch Service&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS 作為「workload generator」的分散式平行化角色。</p>
<h2 id="觀察">觀察</h2>
<p>SmugMug 用 SQS 兩種模式：(1) backfill — script 推 DynamoDB scan-segment 指令進 SQS、Lambda 拉取做平行掃描寫 OpenSearch、(2) 鏡像查詢 — production query 推副本 SQS、Lambda replay 到 replica domain。每小時可 index &gt; 1 billion document、不影響 production。</p>
<h2 id="判讀">判讀</h2>
<p>SQS 作為「workload generator」分散式平行化、不需協調 worker 數量。揭露 SQS 不只是「事件 queue」、也是「並行任務分散」的協調基礎。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard queue / Long polling / SQS + Lambda event source。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/smugmugs-durable-search-pipelines-for-amazon-opensearch-service/">SmugMug&rsquo;s Durable Search Pipelines for Amazon OpenSearch Service</a></li>
</ul>
]]></content:encoded></item><item><title>3.C56 PostNL EBE：完整 DLQ + retention + redrive 設計</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/</guid><description>&lt;p>這個案例的核心責任是業內真正完整的 DLQ + redrive + retention 設計案例、不是 demo 規模。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PostNL（荷蘭最大物流商、每天 6.9M 信件 + 1.1M 包裹）的 Event Broker E-commerce 系統每天處理 ~10M message。完整列出 SQS 配置：每 producer/consumer 隔離 stack（最小爆炸半徑）、3 天 replay via EventBridge、exponential backoff with jitter、24 小時內最多 retry 100 次、final DLQ 允許 consumer 自己 redrive。max receive count 設 1 觸發 DLQ 告警。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「每 producer/consumer 隔離 stack」是 mission-critical 系統的 blast radius 設計、不只是 queue 配置。揭露 production-grade SQS 設計含三件事：隔離 + retry 政策 + redrive 流程。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：DLQ 設計 / CloudWatch alarm / Cost 模型。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/postnl-engineering/design-a-mission-critical-serverless-application-for-high-resilience-2858bf11360a">Designing a Mission-Critical Serverless Application for High Resilience&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是業內真正完整的 DLQ + redrive + retention 設計案例、不是 demo 規模。</p>
<h2 id="觀察">觀察</h2>
<p>PostNL（荷蘭最大物流商、每天 6.9M 信件 + 1.1M 包裹）的 Event Broker E-commerce 系統每天處理 ~10M message。完整列出 SQS 配置：每 producer/consumer 隔離 stack（最小爆炸半徑）、3 天 replay via EventBridge、exponential backoff with jitter、24 小時內最多 retry 100 次、final DLQ 允許 consumer 自己 redrive。max receive count 設 1 觸發 DLQ 告警。</p>
<h2 id="判讀">判讀</h2>
<p>「每 producer/consumer 隔離 stack」是 mission-critical 系統的 blast radius 設計、不只是 queue 配置。揭露 production-grade SQS 設計含三件事：隔離 + retry 政策 + redrive 流程。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：DLQ 設計 / CloudWatch alarm / Cost 模型。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/postnl-engineering/design-a-mission-critical-serverless-application-for-high-resilience-2858bf11360a">Designing a Mission-Critical Serverless Application for High Resilience</a></li>
</ul>
]]></content:encoded></item><item><title>3.C57 Lob：自家 fork @lob/sqs-consumer 修 FIFO bug</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/</guid><description>&lt;p>這個案例的核心責任是說明真實 production library 維護成本、FIFO consumer 的隱性 bug。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Lob（programmatic mail API）原本用 bbc/sqs-consumer 但被鎖在 AWS SDK v2。他們 fork 出 @lob/sqs-consumer：支援 SDK v3（模組化 import 縮 bundle、TypeScript 一級支援、async/await）、修正原 library 對 FIFO queue 的 bug。SQS 用在 Lob API 跟其他內部 service。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不能只靠 SDK 原生 API、SDK 升級會逼出 library 維護議題。揭露「FIFO queue 跟 standard queue 的 client 行為差異」是 library 層的隱性 bug 來源。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / Long polling / Client library 維護。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.lob.com/blog/lob-sqs-consumer">@lob/sqs-consumer&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明真實 production library 維護成本、FIFO consumer 的隱性 bug。</p>
<h2 id="觀察">觀察</h2>
<p>Lob（programmatic mail API）原本用 bbc/sqs-consumer 但被鎖在 AWS SDK v2。他們 fork 出 @lob/sqs-consumer：支援 SDK v3（模組化 import 縮 bundle、TypeScript 一級支援、async/await）、修正原 library 對 FIFO queue 的 bug。SQS 用在 Lob API 跟其他內部 service。</p>
<h2 id="判讀">判讀</h2>
<p>不能只靠 SDK 原生 API、SDK 升級會逼出 library 維護議題。揭露「FIFO queue 跟 standard queue 的 client 行為差異」是 library 層的隱性 bug 來源。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / Long polling / Client library 維護。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.lob.com/blog/lob-sqs-consumer">@lob/sqs-consumer</a></li>
</ul>
]]></content:encoded></item><item><title>3.C58 Twilio：SQS 緩衝高流量 webhook</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/</guid><description>&lt;p>這個案例的核心責任是說明 webhook → SQS buffer 是 Twilio 推薦的 pattern、FIFO TPS 上限的分片實務。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twilio 自己 engineering blog 教使用者用 SQS 緩衝來自 Twilio 的高流量 SMS / status callback webhook（避免下游 app 來不及處理）。用 separate queue 區分 SMS vs status callback、long polling 減少空 API call、特別點出 FIFO 300 TPS 上限要分 queue。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Webhook 是 push、下游可能來不及、SQS 當 buffer 是常見 pattern。揭露 FIFO 的 300 TPS 上限是 hard limit、要設計分片才能擴張。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Long polling / Standard vs FIFO。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.twilio.com/en-us/blog/handling-high-volume-inbound-sms-and-webhooks-with-twilio-functions-and-amazon-sqs-html">Handling High Volume Inbound SMS and Webhooks with Twilio Functions and Amazon SQS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 webhook → SQS buffer 是 Twilio 推薦的 pattern、FIFO TPS 上限的分片實務。</p>
<h2 id="觀察">觀察</h2>
<p>Twilio 自己 engineering blog 教使用者用 SQS 緩衝來自 Twilio 的高流量 SMS / status callback webhook（避免下游 app 來不及處理）。用 separate queue 區分 SMS vs status callback、long polling 減少空 API call、特別點出 FIFO 300 TPS 上限要分 queue。</p>
<h2 id="判讀">判讀</h2>
<p>Webhook 是 push、下游可能來不及、SQS 當 buffer 是常見 pattern。揭露 FIFO 的 300 TPS 上限是 hard limit、要設計分片才能擴張。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Long polling / Standard vs FIFO。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.twilio.com/en-us/blog/handling-high-volume-inbound-sms-and-webhooks-with-twilio-functions-and-amazon-sqs-html">Handling High Volume Inbound SMS and Webhooks with Twilio Functions and Amazon SQS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C59 Rapid7：SQS 100 億 message/day 規模</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/</guid><description>&lt;p>這個案例的核心責任是建立 SQS 在 10 billion+/day 規模下的成本結構與量級參考點。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Rapid7 Platform Software Architect 公開引述：「SQS 是我們架構的關鍵元件、讓我們 scale 到處理 10s of billions of messages per day。」是 AWS 官方文中具名客戶 quote、非 marketing 概括。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SQS 在百億訊息/日規模下仍可用、是 scale 的具體量級參考點。揭露 SQS request-based 計費在這個規模下、cost 模型該被認真評估。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Cost 模型 / Standard queue。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本取捨&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/aws/amazon-sqs-15-years-and-still-queueing/">Amazon SQS — 15 Years and Still Queueing&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是建立 SQS 在 10 billion+/day 規模下的成本結構與量級參考點。</p>
<h2 id="觀察">觀察</h2>
<p>Rapid7 Platform Software Architect 公開引述：「SQS 是我們架構的關鍵元件、讓我們 scale 到處理 10s of billions of messages per day。」是 AWS 官方文中具名客戶 quote、非 marketing 概括。</p>
<h2 id="判讀">判讀</h2>
<p>SQS 在百億訊息/日規模下仍可用、是 scale 的具體量級參考點。揭露 SQS request-based 計費在這個規模下、cost 模型該被認真評估。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Cost 模型 / Standard queue。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本取捨</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/aws/amazon-sqs-15-years-and-still-queueing/">Amazon SQS — 15 Years and Still Queueing</a></li>
</ul>
]]></content:encoded></item><item><title>3.C60 Spotify：Event Delivery 從 Kafka 遷到 Pub/Sub</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/</guid><description>&lt;p>Spotify 把全球 event delivery 從 Kafka 遷到 Cloud Pub/Sub 的案例揭露了大規模 pull subscription 的工程現實 — at-least-once 語意意味著應用層去重不可省。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Spotify 的 Event Delivery 系統負責把所有使用者行為事件（播放、搜尋、推薦互動、廣告曝光）從客戶端經由資料管線送到下游消費者。事件是推薦引擎、A/B test、廣告計費跟 analytics 的核心輸入。&lt;/p>
&lt;p>遷移到 GCP Pub/Sub 後的系統規模：每個 event type 一個 topic、~15 個 microservice 跑在 ~2500 VM 上、Q1 2019 高峰 8M events/sec、每日 350 TB raw event 流量。遷出 Kafka 的動機跟技術評估見 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka（反例）&lt;/a>。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="at-least-once-語意下的重複">At-least-once 語意下的重複&lt;/h3>
&lt;p>Cloud Pub/Sub（早期版本）提供 at-least-once delivery — 同一筆訊息可能被 deliver 多次。在每日 350 TB 的流量下，「偶爾重複」的頻率足以影響 analytics 數據跟廣告計費的準確性。&lt;/p>
&lt;p>Pub/Sub 的重複來源有兩個：ack deadline 到期前 consumer 還沒處理完、訊息被重新 deliver 給其他 consumer；以及 Pub/Sub backend 的內部 redelivery（罕見但非零）。&lt;/p>
&lt;h3 id="pull-subscription-的流控">Pull subscription 的流控&lt;/h3>
&lt;p>Pull subscription 讓 consumer 主動從 Pub/Sub 拉取訊息（vs push subscription 由 Pub/Sub 推送到 HTTP endpoint）。Pull 的好處是 consumer 可以控制自己的消費速度，避免被推送壓垮。&lt;/p>
&lt;p>大規模 pull subscription 的挑戰在於流控的精細度 — 每個 consumer VM 要設定合理的 maxOutstandingMessages 跟 maxOutstandingBytes，太大會讓 consumer 記憶體不足、太小會浪費 Pub/Sub 的吞吐能力。Spotify 的 2500 VM 各自獨立做 pull，需要在 fleet 級別保持流控的一致性。&lt;/p>
&lt;h3 id="每個-event-type-一個-topic-的治理">每個 event type 一個 topic 的治理&lt;/h3>
&lt;p>Spotify 按 event type 建立 topic（例如 &lt;code>play-event&lt;/code>、&lt;code>search-event&lt;/code>、&lt;code>ad-impression&lt;/code>）。Event type 數量成長後，topic 數量跟著增長。每個 topic 需要獨立的 subscription、monitoring、ack deadline 設定跟 retention policy。&lt;/p>
&lt;p>Topic 治理的工程問題是「誰 own 這個 topic、schema 變更怎麼協調、retention 該設多久」。Spotify 自建了 event delivery 平台層（Event Delivery Platform）來管理 topic lifecycle — 包括 topic 建立 / 刪除的 self-service API、schema registry、consumer group 管理。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="自建-deduplication-層">自建 deduplication 層&lt;/h3>
&lt;p>Spotify 在 consumer 端自建去重機制。每筆 event 帶 unique event ID，consumer 在處理前查 dedup store（記憶體 + 外部 cache）確認是否已處理過。已處理的 event 直接 ack、跳過處理邏輯。&lt;/p></description><content:encoded><![CDATA[<p>Spotify 把全球 event delivery 從 Kafka 遷到 Cloud Pub/Sub 的案例揭露了大規模 pull subscription 的工程現實 — at-least-once 語意意味著應用層去重不可省。</p>
<h2 id="業務背景">業務背景</h2>
<p>Spotify 的 Event Delivery 系統負責把所有使用者行為事件（播放、搜尋、推薦互動、廣告曝光）從客戶端經由資料管線送到下游消費者。事件是推薦引擎、A/B test、廣告計費跟 analytics 的核心輸入。</p>
<p>遷移到 GCP Pub/Sub 後的系統規模：每個 event type 一個 topic、~15 個 microservice 跑在 ~2500 VM 上、Q1 2019 高峰 8M events/sec、每日 350 TB raw event 流量。遷出 Kafka 的動機跟技術評估見 <a href="/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka（反例）</a>。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="at-least-once-語意下的重複">At-least-once 語意下的重複</h3>
<p>Cloud Pub/Sub（早期版本）提供 at-least-once delivery — 同一筆訊息可能被 deliver 多次。在每日 350 TB 的流量下，「偶爾重複」的頻率足以影響 analytics 數據跟廣告計費的準確性。</p>
<p>Pub/Sub 的重複來源有兩個：ack deadline 到期前 consumer 還沒處理完、訊息被重新 deliver 給其他 consumer；以及 Pub/Sub backend 的內部 redelivery（罕見但非零）。</p>
<h3 id="pull-subscription-的流控">Pull subscription 的流控</h3>
<p>Pull subscription 讓 consumer 主動從 Pub/Sub 拉取訊息（vs push subscription 由 Pub/Sub 推送到 HTTP endpoint）。Pull 的好處是 consumer 可以控制自己的消費速度，避免被推送壓垮。</p>
<p>大規模 pull subscription 的挑戰在於流控的精細度 — 每個 consumer VM 要設定合理的 maxOutstandingMessages 跟 maxOutstandingBytes，太大會讓 consumer 記憶體不足、太小會浪費 Pub/Sub 的吞吐能力。Spotify 的 2500 VM 各自獨立做 pull，需要在 fleet 級別保持流控的一致性。</p>
<h3 id="每個-event-type-一個-topic-的治理">每個 event type 一個 topic 的治理</h3>
<p>Spotify 按 event type 建立 topic（例如 <code>play-event</code>、<code>search-event</code>、<code>ad-impression</code>）。Event type 數量成長後，topic 數量跟著增長。每個 topic 需要獨立的 subscription、monitoring、ack deadline 設定跟 retention policy。</p>
<p>Topic 治理的工程問題是「誰 own 這個 topic、schema 變更怎麼協調、retention 該設多久」。Spotify 自建了 event delivery 平台層（Event Delivery Platform）來管理 topic lifecycle — 包括 topic 建立 / 刪除的 self-service API、schema registry、consumer group 管理。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="自建-deduplication-層">自建 deduplication 層</h3>
<p>Spotify 在 consumer 端自建去重機制。每筆 event 帶 unique event ID，consumer 在處理前查 dedup store（記憶體 + 外部 cache）確認是否已處理過。已處理的 event 直接 ack、跳過處理邏輯。</p>
<p>Dedup store 的挑戰是大小跟 TTL — 要記住多久以前的 event ID 才夠。TTL 太短會漏掉 late redelivery（Pub/Sub 在 ack deadline 之後才重新 deliver）、TTL 太長 dedup store 太大。Spotify 用滑動視窗（retention 跟 ack deadline 的倍數）設定 TTL。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Pub/Sub + 自建 dedup</th>
          <th>自管 Kafka 0.8+</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>運維成本</td>
          <td>低（Pub/Sub 全託管）</td>
          <td>高（自管 broker × 多 region）</td>
      </tr>
      <tr>
          <td>語意保證</td>
          <td>At-least-once + 應用層 dedup</td>
          <td>At-least-once（idempotent 0.11+）</td>
      </tr>
      <tr>
          <td>跨 region replication</td>
          <td>原生支援</td>
          <td>需要 MirrorMaker 或自建</td>
      </tr>
      <tr>
          <td>流控精細度</td>
          <td>Pull subscription 可控</td>
          <td>Consumer group 自動分配</td>
      </tr>
      <tr>
          <td>Topic 治理</td>
          <td>需要自建平台層</td>
          <td>Kafka 生態工具（Confluent 等）</td>
      </tr>
      <tr>
          <td>Dedup 成本</td>
          <td>額外的 cache / store 成本</td>
          <td>Idempotent producer 減少需求</td>
      </tr>
  </tbody>
</table>
<p>自建 dedup 的成本是 Spotify 選 Pub/Sub 的額外付出。這個代價在託管方案的運維節省面前被接受 — 維護一個 dedup cache 的成本遠低於維護跨 5 個 datacenter 的 Kafka broker fleet。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a>：push vs pull subscription、ack deadline、ordering 跟 DLT 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka</a>：遷出 Kafka 的動機跟決策判準</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：at-least-once 語意下的 dedup 策略</li>
<li><a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract replay boundary</a>：event schema 跟 topic lifecycle 的治理</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用 GCP Pub/Sub 且下游消費者偶爾處理到重複事件</li>
<li>Pull subscription 的 consumer 記憶體使用不穩定、maxOutstandingMessages 設定不合理</li>
<li>Topic 數量持續增長但缺少統一的 lifecycle 管理</li>
<li>從自管 Kafka 遷移到 GCP Pub/Sub 的評估階段</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2019/11/spotifys-event-delivery-life-in-the-cloud">Spotify&rsquo;s Event Delivery — Life in the Cloud</a></li>
</ul>
]]></content:encoded></item><item><title>3.C61 Spotify：Autoscaling Pub/Sub consumer 反效果</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/</guid><description>&lt;p>這個案例的核心責任是說明「subscription backlog 不等於 consumer healthy」、autoscaling 跟 ack deadline 的耦合風險。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>下游 Cloud Storage export 失敗時、consumer 不 ack 仍持續消耗 CPU 處理同批訊息、造成 autoscaling 把 CPU 越拉越高的反效果；解法是 exponential backoff 抑制 CPU 消耗。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「Subscription backlog 不等於 consumer healthy」— 訊息未 ack 累積跟 autoscaling 的耦合風險。揭露 autoscale signal 該看「處理成功率」而非「CPU + backlog」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / autoscaling signal 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.atspotify.com/2017/11/autoscaling-pub-sub-consumers">Autoscaling Pub/Sub Consumers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「subscription backlog 不等於 consumer healthy」、autoscaling 跟 ack deadline 的耦合風險。</p>
<h2 id="觀察">觀察</h2>
<p>下游 Cloud Storage export 失敗時、consumer 不 ack 仍持續消耗 CPU 處理同批訊息、造成 autoscaling 把 CPU 越拉越高的反效果；解法是 exponential backoff 抑制 CPU 消耗。</p>
<h2 id="判讀">判讀</h2>
<p>「Subscription backlog 不等於 consumer healthy」— 訊息未 ack 累積跟 autoscaling 的耦合風險。揭露 autoscale signal 該看「處理成功率」而非「CPU + backlog」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / autoscaling signal 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/11/autoscaling-pub-sub-consumers">Autoscaling Pub/Sub Consumers</a></li>
</ul>
]]></content:encoded></item><item><title>Pinterest：快取可靠性與容量驚奇治理</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/</guid><description>&lt;p>Pinterest 案例的核心責任是處理快取層造成的容量驚奇。快取命中率下滑會在短時間放大到資料層與下游依賴，因此需要預先設計退化與重建節奏。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>流量高峰或快取失溫時，回源壓力會瞬間上升。若沒有緩衝機制與重建策略，系統容易進入連鎖退化。&lt;/p>
&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>Cache headroom&lt;/td>
 &lt;td>命中率下滑能承受多久&lt;/td>
 &lt;td>容量緩衝&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Graceful degradation&lt;/td>
 &lt;td>快取失效時如何降級&lt;/td>
 &lt;td>服務連續性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rewarm strategy&lt;/td>
 &lt;td>熱資料如何有序回填&lt;/td>
 &lt;td>恢復節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>cache hit ratio drop&lt;/td>
 &lt;td>是否進入危險區&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>fallback latency&lt;/td>
 &lt;td>降級路徑是否可接受&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rewarm backlog&lt;/td>
 &lt;td>回填是否可收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2&lt;/a> 模擬命中率崩落，再把恢復證據寫入 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Pinterest 案例的核心責任是處理快取層造成的容量驚奇。快取命中率下滑會在短時間放大到資料層與下游依賴，因此需要預先設計退化與重建節奏。</p>
<h2 id="問題場景">問題場景</h2>
<p>流量高峰或快取失溫時，回源壓力會瞬間上升。若沒有緩衝機制與重建策略，系統容易進入連鎖退化。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cache headroom</td>
          <td>命中率下滑能承受多久</td>
          <td>容量緩衝</td>
      </tr>
      <tr>
          <td>Graceful degradation</td>
          <td>快取失效時如何降級</td>
          <td>服務連續性</td>
      </tr>
      <tr>
          <td>Rewarm strategy</td>
          <td>熱資料如何有序回填</td>
          <td>恢復節奏</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>cache hit ratio drop</td>
          <td>是否進入危險區</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a></td>
      </tr>
      <tr>
          <td>fallback latency</td>
          <td>降級路徑是否可接受</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>rewarm backlog</td>
          <td>回填是否可收斂</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>先在 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2</a> 模擬命中率崩落，再把恢復證據寫入 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a>。</p>
]]></content:encoded></item><item><title>Reddit：2023 Kubernetes 升級事故</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/</guid><description>&lt;p>這起案例的核心責任是把平台升級納入事故流程。升級事件不是純部署問題，會直接影響事件分級、回退與通訊節奏。&lt;/p>
&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>post-upgrade error burst&lt;/td>
 &lt;td>變更後退化是否快速擴散&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollback decision delay&lt;/td>
 &lt;td>回退決策是否過慢&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>service recovery slope&lt;/td>
 &lt;td>恢復是否分批收斂&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「平台升級變更」與「事故分級決策」要共用同一套欄位。主要風險是把升級當例行操作，延後回退判斷。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>把升級變更與事故決策共用欄位，並在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8&lt;/a> 加入升級專屬 gate。事故收斂後回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是把平台升級納入事故流程。升級事件不是純部署問題，會直接影響事件分級、回退與通訊節奏。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>post-upgrade error burst</td>
          <td>變更後退化是否快速擴散</td>
          <td><a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1</a></td>
      </tr>
      <tr>
          <td>rollback decision delay</td>
          <td>回退決策是否過慢</td>
          <td><a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a></td>
      </tr>
      <tr>
          <td>service recovery slope</td>
          <td>恢復是否分批收斂</td>
          <td><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「平台升級變更」與「事故分級決策」要共用同一套欄位。主要風險是把升級當例行操作，延後回退判斷。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>把升級變更與事故決策共用欄位，並在 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8</a> 加入升級專屬 gate。事故收斂後回寫 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a>。</p>
]]></content:encoded></item><item><title>3.C62 Spotify：Pub/Sub → GCS reliable export</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/</guid><description>&lt;p>這個案例的核心責任是說明 ack 是 end-to-end commit 信號、不是 buffer-flush 信號。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Consumer 只在下游 Completionist 回 200 OK 才 ack 回 Pub/Sub、並用「Oldest Unacknowledged Message」metric 判斷 hourly bucket 何時可安全關閉；ack semantics 直接綁定下游 commit。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>ack 是 end-to-end commit 信號、不是 buffer-flush 信號。揭露為什麼後來原生 GCS subscription 有價值（Spotify 早期沒有原生、自建管線）。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / Cloud Storage subscription（早期無原生、自建對照）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.atspotify.com/2017/04/reliable-export-of-cloud-pubsub-streams-to-cloud-storage">Reliable Export of Cloud Pub/Sub Streams to Cloud Storage&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 ack 是 end-to-end commit 信號、不是 buffer-flush 信號。</p>
<h2 id="觀察">觀察</h2>
<p>Consumer 只在下游 Completionist 回 200 OK 才 ack 回 Pub/Sub、並用「Oldest Unacknowledged Message」metric 判斷 hourly bucket 何時可安全關閉；ack semantics 直接綁定下游 commit。</p>
<h2 id="判讀">判讀</h2>
<p>ack 是 end-to-end commit 信號、不是 buffer-flush 信號。揭露為什麼後來原生 GCS subscription 有價值（Spotify 早期沒有原生、自建管線）。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / Cloud Storage subscription（早期無原生、自建對照）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/04/reliable-export-of-cloud-pubsub-streams-to-cloud-storage">Reliable Export of Cloud Pub/Sub Streams to Cloud Storage</a></li>
</ul>
]]></content:encoded></item><item><title>3.C63 Mercari Actionable History：ack deadline 是 batch-level</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/</guid><description>&lt;p>這個案例的核心責任是揭露 Pub/Sub client lib 「ack deadline 是 batch-level」這個真實的工程陷阱。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Merpay 支付流水帳服務用 Pub/Sub 做 async messaging、靠 nack 控制處理順序；踩到「ack deadline 是整批 batch 而非單訊息」、acked 訊息會跟同 batch 其他 expired/nacked 訊息一起 redeliver 的設計細節。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「ack deadline 是 batch-level」是 Pub/Sub client lib 真實的工程陷阱；idempotency 是處理 duplicate 的必要設計、新出的 exactly-once delivery 才有機會降低重複量。揭露 client lib 的批次語意會「污染」單訊息 ack。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / Push vs Pull / Ordering key（exactly-once / ordering 章節）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20221212-merpay-actionable-history-displaying-millions-of-payments-with-lightning-speed/">Merpay Actionable History: Displaying Millions of Payments with Lightning Speed&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭露 Pub/Sub client lib 「ack deadline 是 batch-level」這個真實的工程陷阱。</p>
<h2 id="觀察">觀察</h2>
<p>Merpay 支付流水帳服務用 Pub/Sub 做 async messaging、靠 nack 控制處理順序；踩到「ack deadline 是整批 batch 而非單訊息」、acked 訊息會跟同 batch 其他 expired/nacked 訊息一起 redeliver 的設計細節。</p>
<h2 id="判讀">判讀</h2>
<p>「ack deadline 是 batch-level」是 Pub/Sub client lib 真實的工程陷阱；idempotency 是處理 duplicate 的必要設計、新出的 exactly-once delivery 才有機會降低重複量。揭露 client lib 的批次語意會「污染」單訊息 ack。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / Push vs Pull / Ordering key（exactly-once / ordering 章節）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20221212-merpay-actionable-history-displaying-millions-of-payments-with-lightning-speed/">Merpay Actionable History: Displaying Millions of Payments with Lightning Speed</a></li>
</ul>
]]></content:encoded></item><item><title>3.C64 Mercari Item Feed：DLT 防 poison message 阻塞</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/</guid><description>&lt;p>這個案例的核心責任是說明 DLT 在防止 poison message 阻塞 pipeline 的角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>商品 feed 同步用 pull subscription + 自家 batch requester、成功時 ack 整批、失敗時 nack 讓 Pub/Sub 重送；重試多次仍失敗則送 Dead-letter topic、後續訊息優先處理；topic 同時當突發流量的緩衝。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>直接示範 DLT 在防止 poison message 阻塞 pipeline 的角色、以及把 topic 當 load-leveling queue 的設計。揭露「topic = buffer + dispatch」雙重角色。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Dead-letter topic / Push vs Pull subscription。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &amp;#43; retention &amp;#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56 PostNL EBE&lt;/a>（DLQ 設計對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/">Mercari&amp;rsquo;s Seamless Item Feed Integration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 DLT 在防止 poison message 阻塞 pipeline 的角色。</p>
<h2 id="觀察">觀察</h2>
<p>商品 feed 同步用 pull subscription + 自家 batch requester、成功時 ack 整批、失敗時 nack 讓 Pub/Sub 重送；重試多次仍失敗則送 Dead-letter topic、後續訊息優先處理；topic 同時當突發流量的緩衝。</p>
<h2 id="判讀">判讀</h2>
<p>直接示範 DLT 在防止 poison message 阻塞 pipeline 的角色、以及把 topic 當 load-leveling queue 的設計。揭露「topic = buffer + dispatch」雙重角色。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Dead-letter topic / Push vs Pull subscription。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &#43; retention &#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56 PostNL EBE</a>（DLQ 設計對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/">Mercari&rsquo;s Seamless Item Feed Integration</a></li>
</ul>
]]></content:encoded></item><item><title>3.C65 Mercari LINE：Pull subscription 對齊外部 RPS</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/</guid><description>&lt;p>這個案例的核心責任是說明「下游有 RPS 限制」是 Pull subscription 勝過 push 的典型情境。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Braze webhook 進來後轉成 Pub/Sub event、下游 LINE worker pull subscription「精確控制每秒處理訊息數」、因為外部 LINE API 有 RPS 限制。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>push 會把流量瞬間打到 endpoint、pull 可由 consumer 自行 throttle。揭露 push vs pull 不是「實作偏好」、是「下游能否接受 push 衝擊」的判讀。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Push vs Pull subscription。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58 Twilio webhook buffer&lt;/a>（webhook + buffer 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/">Flow Control Challenges in Mercari&amp;rsquo;s LINE Integration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「下游有 RPS 限制」是 Pull subscription 勝過 push 的典型情境。</p>
<h2 id="觀察">觀察</h2>
<p>Braze webhook 進來後轉成 Pub/Sub event、下游 LINE worker pull subscription「精確控制每秒處理訊息數」、因為外部 LINE API 有 RPS 限制。</p>
<h2 id="判讀">判讀</h2>
<p>push 會把流量瞬間打到 endpoint、pull 可由 consumer 自行 throttle。揭露 push vs pull 不是「實作偏好」、是「下游能否接受 push 衝擊」的判讀。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Push vs Pull subscription。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58 Twilio webhook buffer</a>（webhook + buffer 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/">Flow Control Challenges in Mercari&rsquo;s LINE Integration</a></li>
</ul>
]]></content:encoded></item><item><title>3.C66 Mercari B2C：自建 PubSub gRPC Pusher</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/</guid><description>&lt;p>這個案例的核心責任是說明原生 push subscription 在特定場景的限制、逼出自建層的工程選擇。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>全球商品同步系統、自建 in-house「PubSub gRPC Pusher」（Pub/Sub 的 gRPC 版 push subscription）解決高吞吐 / 長 job / 彈性 RPS；同時用 message ID 做去重、timestamp 驗證解決重複 + 亂序。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>原生 HTTP push subscription 在「長 job + 高吞吐 + 動態 rate」場景的限制、逼出自建層的工程選擇。揭露 managed broker 的「原生功能」不是所有場景的終點。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Push vs Pull subscription / Ordering key（亂序的 application-level 處理）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/">From Local to Global: Building Seamless B2C Product Integration at Mercari&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明原生 push subscription 在特定場景的限制、逼出自建層的工程選擇。</p>
<h2 id="觀察">觀察</h2>
<p>全球商品同步系統、自建 in-house「PubSub gRPC Pusher」（Pub/Sub 的 gRPC 版 push subscription）解決高吞吐 / 長 job / 彈性 RPS；同時用 message ID 做去重、timestamp 驗證解決重複 + 亂序。</p>
<h2 id="判讀">判讀</h2>
<p>原生 HTTP push subscription 在「長 job + 高吞吐 + 動態 rate」場景的限制、逼出自建層的工程選擇。揭露 managed broker 的「原生功能」不是所有場景的終點。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Push vs Pull subscription / Ordering key（亂序的 application-level 處理）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/">From Local to Global: Building Seamless B2C Product Integration at Mercari</a></li>
</ul>
]]></content:encoded></item><item><title>3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/</guid><description>&lt;p>這個案例的核心責任是說明大規模遊戲 telemetry 的 ingest backbone 設計。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pokémon GO frontend 把玩家事件 publish 到 Pub/Sub topic 餵分析 pipeline、再進 BigQuery streaming；高峰 ~1M TPS、Pub/Sub 是 managed service 因此 SRE 維運成本低。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Pub/Sub 在 publisher 突發流量下作為 elastic buffer、下游 BigQuery streaming 是常見組合。揭露「managed service 的 SRE 成本」是大規模遊戲場景的關鍵選型理由。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：BigQuery subscription（原生 BQ subscription 出現前的 Dataflow pattern）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &amp;#43; Dataflow &amp;#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &amp;lt; 100ms、BigQuery 並行存 raw recovery。">3.C68 Wix clickstream&lt;/a>（同類組合）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/how-pok%C3%A9mon-go-scales-millions-requests">How Pokémon GO Scales to Millions of Requests&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模遊戲 telemetry 的 ingest backbone 設計。</p>
<h2 id="觀察">觀察</h2>
<p>Pokémon GO frontend 把玩家事件 publish 到 Pub/Sub topic 餵分析 pipeline、再進 BigQuery streaming；高峰 ~1M TPS、Pub/Sub 是 managed service 因此 SRE 維運成本低。</p>
<h2 id="判讀">判讀</h2>
<p>Pub/Sub 在 publisher 突發流量下作為 elastic buffer、下游 BigQuery streaming 是常見組合。揭露「managed service 的 SRE 成本」是大規模遊戲場景的關鍵選型理由。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：BigQuery subscription（原生 BQ subscription 出現前的 Dataflow pattern）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &#43; Dataflow &#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &lt; 100ms、BigQuery 並行存 raw recovery。">3.C68 Wix clickstream</a>（同類組合）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/topics/developers-practitioners/how-pok%C3%A9mon-go-scales-millions-requests">How Pokémon GO Scales to Millions of Requests</a></li>
</ul>
]]></content:encoded></item><item><title>3.C68 Wix：Pub/Sub decouple + Dataflow + BQ archive</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/</guid><description>&lt;p>這個案例的核心責任是「Pub/Sub buffer + Dataflow stream processor + BQ archive」的教科書組合。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>App Engine 收 clickstream → 進 Cloud Pub/Sub queue、再由 Dataflow streaming 處理進 Datastore、dashboard 端到端 latency &amp;lt; 100ms；BigQuery 並行存 raw data 做 recovery。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「Pub/Sub 當 decouple buffer + Dataflow 當 stream processor + BigQuery 當 raw archive」的 textbook 組合、可作為 BigQuery subscription 出現前的對比 case（為什麼後來原生 BQ subscription 能省掉 Dataflow 中介層）。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：BigQuery subscription / Push vs Pull。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67 Niantic Pokémon GO&lt;/a>（同類組合）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/customers/wix">Wix Customer Story&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是「Pub/Sub buffer + Dataflow stream processor + BQ archive」的教科書組合。</p>
<h2 id="觀察">觀察</h2>
<p>App Engine 收 clickstream → 進 Cloud Pub/Sub queue、再由 Dataflow streaming 處理進 Datastore、dashboard 端到端 latency &lt; 100ms；BigQuery 並行存 raw data 做 recovery。</p>
<h2 id="判讀">判讀</h2>
<p>「Pub/Sub 當 decouple buffer + Dataflow 當 stream processor + BigQuery 當 raw archive」的 textbook 組合、可作為 BigQuery subscription 出現前的對比 case（為什麼後來原生 BQ subscription 能省掉 Dataflow 中介層）。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：BigQuery subscription / Push vs Pull。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67 Niantic Pokémon GO</a>（同類組合）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/wix">Wix Customer Story</a></li>
</ul>
]]></content:encoded></item><item><title>3.C69 Twitter Ad Engagement：把 stream 切成多 topic 做 partition</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/</guid><description>&lt;p>這個案例的核心責任是說明 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twitter 把 on-prem 服務的 Avro-formatted 訊息 push 到 Pub/Sub（兩條 stream、較不關鍵但量大的那條 ~80K msg/s 切成 6 個 topic）、下游用 Dataflow + Beam 處理進 Bigtable / BigQuery。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「把單一 high-volume stream 切成多 topic 做 partition」是 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。揭露 Pub/Sub 跟 Kafka 的選型差異不是 feature parity、是不同的擴張模型。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Schema enforcement（Avro 是常見 schema 候選）/ Ordering key（topic 切分 vs ordering key 的取捨）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（partition 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/data-analytics/modernizing-twitters-ad-engagement-analytics-platform">Modernizing Twitter&amp;rsquo;s Ad Engagement Analytics Platform&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。</p>
<h2 id="觀察">觀察</h2>
<p>Twitter 把 on-prem 服務的 Avro-formatted 訊息 push 到 Pub/Sub（兩條 stream、較不關鍵但量大的那條 ~80K msg/s 切成 6 個 topic）、下游用 Dataflow + Beam 處理進 Bigtable / BigQuery。</p>
<h2 id="判讀">判讀</h2>
<p>「把單一 high-volume stream 切成多 topic 做 partition」是 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。揭露 Pub/Sub 跟 Kafka 的選型差異不是 feature parity、是不同的擴張模型。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Schema enforcement（Avro 是常見 schema 候選）/ Ordering key（topic 切分 vs ordering key 的取捨）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（partition 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/data-analytics/modernizing-twitters-ad-engagement-analytics-platform">Modernizing Twitter&rsquo;s Ad Engagement Analytics Platform</a></li>
</ul>
]]></content:encoded></item><item><title>Microsoft 365：套件級身分驗證事故</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/</guid><description>&lt;p>這起案例的核心責任是處理跨產品套件的共同依賴風險。企業套件事故常同時影響 mail、collaboration 與 admin 能力，影響評估必須快速分層。&lt;/p>
&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>cross-product auth errors&lt;/td>
 &lt;td>影響是否跨產品同步出現&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>admin-plane availability&lt;/td>
 &lt;td>管理平面是否可用&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/vendor-dependency-incident/" data-link-title="8.15 Vendor / 第三方依賴事故處理" data-link-desc="依賴方掛掉、自己無 control 時的決策模型">8.15&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>communication consistency&lt;/td>
 &lt;td>對外狀態是否一致&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這個案例的邊界是「套件級共同依賴失效」，不是單一產品缺陷。主要風險是把跨產品事件拆成局部事件，導致對外訊息與修復順序失焦。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先做產品分層影響盤點，再把指揮決策與外部更新同步回寫 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22&lt;/a>。若影響評估不一致，先補 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20&lt;/a> 再更新對外節奏。&lt;/p></description><content:encoded><![CDATA[<p>這起案例的核心責任是處理跨產品套件的共同依賴風險。企業套件事故常同時影響 mail、collaboration 與 admin 能力，影響評估必須快速分層。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>回寫章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>cross-product auth errors</td>
          <td>影響是否跨產品同步出現</td>
          <td><a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a></td>
      </tr>
      <tr>
          <td>admin-plane availability</td>
          <td>管理平面是否可用</td>
          <td><a href="/blog/backend/08-incident-response/vendor-dependency-incident/" data-link-title="8.15 Vendor / 第三方依賴事故處理" data-link-desc="依賴方掛掉、自己無 control 時的決策模型">8.15</a></td>
      </tr>
      <tr>
          <td>communication consistency</td>
          <td>對外狀態是否一致</td>
          <td><a href="/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10</a></td>
      </tr>
  </tbody>
</table>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這個案例的邊界是「套件級共同依賴失效」，不是單一產品缺陷。主要風險是把跨產品事件拆成局部事件，導致對外訊息與修復順序失焦。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先做產品分層影響盤點，再把指揮決策與外部更新同步回寫 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22</a>。若影響評估不一致，先補 <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20</a> 再更新對外節奏。</p>
]]></content:encoded></item><item><title>Spotify：平台工程與可靠性契約</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/</guid><description>&lt;p>Spotify 案例的核心責任是把可靠性標準平台化。當團隊自治程度高，若沒有共同契約，跨服務風險會在整合時爆發。&lt;/p>
&lt;h2 id="問題場景">問題場景&lt;/h2>
&lt;p>不同團隊採用不同部署與觀測習慣，單隊看似穩定，但跨服務路徑會出現隱性斷點，導致事故時難以協同定位。&lt;/p>
&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>Reliability contract&lt;/td>
 &lt;td>每個服務最低要提供什麼&lt;/td>
 &lt;td>基線能力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Platform self-service&lt;/td>
 &lt;td>標準如何降低導入成本&lt;/td>
 &lt;td>擴散能力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cross-team evidence&lt;/td>
 &lt;td>證據如何跨團隊共享&lt;/td>
 &lt;td>協作效率&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>contract compliance rate&lt;/td>
 &lt;td>契約覆蓋是否足夠&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>release dependency failures&lt;/td>
 &lt;td>依賴變更是否常破壞發布&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cross-team incident handoff latency&lt;/td>
 &lt;td>交接是否有共同語言&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先補 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10&lt;/a> 的契約欄位，再以 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18&lt;/a> 對齊 owner 與責任邊界。&lt;/p></description><content:encoded><![CDATA[<p>Spotify 案例的核心責任是把可靠性標準平台化。當團隊自治程度高，若沒有共同契約，跨服務風險會在整合時爆發。</p>
<h2 id="問題場景">問題場景</h2>
<p>不同團隊採用不同部署與觀測習慣，單隊看似穩定，但跨服務路徑會出現隱性斷點，導致事故時難以協同定位。</p>
<h2 id="決策機制">決策機制</h2>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>核心問題</th>
          <th>交付結果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Reliability contract</td>
          <td>每個服務最低要提供什麼</td>
          <td>基線能力</td>
      </tr>
      <tr>
          <td>Platform self-service</td>
          <td>標準如何降低導入成本</td>
          <td>擴散能力</td>
      </tr>
      <tr>
          <td>Cross-team evidence</td>
          <td>證據如何跨團隊共享</td>
          <td>協作效率</td>
      </tr>
  </tbody>
</table>
<h2 id="可觀測訊號">可觀測訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>contract compliance rate</td>
          <td>契約覆蓋是否足夠</td>
          <td><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10</a></td>
      </tr>
      <tr>
          <td>release dependency failures</td>
          <td>依賴變更是否常破壞發布</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a></td>
      </tr>
      <tr>
          <td>cross-team incident handoff latency</td>
          <td>交接是否有共同語言</td>
          <td><a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2</a></td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<p>先補 <a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10</a> 的契約欄位，再以 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a> 對齊 owner 與責任邊界。</p>
]]></content:encoded></item><item><title>可觀測性案例正文</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/</guid><description>&lt;p>這個資料夾的核心責任是把觀測案例變成可回寫章節。案例表格提供線索，正文負責輸出訊號邊界與路由。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">4.C1&lt;/a>&lt;/td>
 &lt;td>FinTech 審計證據觀測&lt;/td>
 &lt;td>把審計與證據鏈變成可觀測訊號&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/" data-link-title="Gaming：高峰流量下的訊號新鮮度與 Cardinality" data-link-desc="在高峰事件中控制訊號延遲與維度爆炸，維持告警與定位可信度。">4.C2&lt;/a>&lt;/td>
 &lt;td>Gaming 高峰訊號治理&lt;/td>
 &lt;td>把高峰流量下訊號失真風險前移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/" data-link-title="Healthcare：存取可追溯性與保留邊界" data-link-desc="在資料主權限制下，建立可追溯存取證據與分層保留策略。">4.C3&lt;/a>&lt;/td>
 &lt;td>Healthcare 存取可追溯性&lt;/td>
 &lt;td>把資料主權場景的存取證據做成治理閉環&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/" data-link-title="4.C4 AWS：X-Ray 到 OpenTelemetry 轉換" data-link-desc="觀測儀表從 vendor-specific SDK 轉向 OpenTelemetry 的治理重點。">4.C4&lt;/a>&lt;/td>
 &lt;td>X-Ray 到 OTel 轉換&lt;/td>
 &lt;td>把觀測遷移標準化成可分段執行流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/" data-link-title="4.C5 Google Cloud：Cloud Trace 導入 OTLP 入口" data-link-desc="觀測平台從專有入口擴展到 OTLP 標準通道的案例。">4.C5&lt;/a>&lt;/td>
 &lt;td>Cloud Trace OTLP 導入&lt;/td>
 &lt;td>把資料通道標準化納入觀測平台治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/" data-link-title="4.C6 AWS：ADOT on EKS 管線遷移" data-link-desc="從分散式 agent 組合轉成 OpenTelemetry collector 管線治理。">4.C6&lt;/a>&lt;/td>
 &lt;td>ADOT on EKS 遷移&lt;/td>
 &lt;td>把 collector/agent 管線轉換成集中治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/datadog-otel-migration-practice/" data-link-title="4.C7 Datadog：OTel 相容遷移實務" data-link-desc="APM 採集從專有代理轉向 OTel 相容模式的治理案例。">4.C7&lt;/a>&lt;/td>
 &lt;td>Datadog OTel 遷移實務&lt;/td>
 &lt;td>把 APM 採集轉成 OTel-compatible 流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/" data-link-title="4.C8 Airbnb：Kubernetes 規模化下的觀測訊號治理" data-link-desc="叢集擴縮與工作負載變動如何回寫觀測模型。">4.C8&lt;/a>&lt;/td>
 &lt;td>Airbnb K8s 規模化訊號&lt;/td>
 &lt;td>把叢集擴縮行為接回觀測與容量治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/" data-link-title="4.C9 反例：OTel 遷移後訊號漂移" data-link-desc="雙軌採集未對齊導致告警與 SLO 判讀失真。">4.C9&lt;/a>&lt;/td>
 &lt;td>反例：OTel 遷移訊號漂移&lt;/td>
 &lt;td>雙軌採集未對齊導致告警與 SLO 判讀失真&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/" data-link-title="4.C10 對照：規模差異下的觀測遷移" data-link-desc="觀測遷移在不同規模團隊下的流程與風險差異。">4.C10&lt;/a>&lt;/td>
 &lt;td>對照：規模差異下觀測遷移&lt;/td>
 &lt;td>不同規模團隊在觀測遷移的風險與流程差異&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/" data-link-title="4.C11 Uber：M3 大規模 Metrics 平台" data-link-desc="從散落的 Prometheus 實例到統一 metrics 平台，處理 cardinality 爆炸、長期 retention 與跨叢集查詢的規模化挑戰。">4.C11&lt;/a>&lt;/td>
 &lt;td>Uber M3 大規模 Metrics&lt;/td>
 &lt;td>從散落的 Prometheus 到統一 metrics 平台&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/" data-link-title="4.C12 Cloudflare：內部觀測平台的三層能力" data-link-desc="全球 300&amp;#43; edge 節點的觀測架構，把 monitoring、analytics 與 forensics 拆成三個獨立能力層。">4.C12&lt;/a>&lt;/td>
 &lt;td>Cloudflare 觀測三層能力&lt;/td>
 &lt;td>monitoring / analytics / forensics 拆分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/" data-link-title="4.C13 Discord：從儲存問題回推觀測缺口" data-link-desc="每次儲存遷移都暴露觀測盲區，把儲存成長問題重新框架為訊號設計問題。">4.C13&lt;/a>&lt;/td>
 &lt;td>Discord 儲存→觀測缺口&lt;/td>
 &lt;td>每次遷移暴露觀測盲區的共同結構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/observability-cost-governance-at-scale/" data-link-title="4.C14 觀測平台成本治理：從帳單驚嚇到可預測成本" data-link-desc="觀測帳單持續超線性成長時，用 cost attribution、cardinality budget、log tiering 跟 adaptive sampling 建立可預測成本模型。">4.C14&lt;/a>&lt;/td>
 &lt;td>觀測成本治理&lt;/td>
 &lt;td>attribution + cardinality budget + tiering&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把觀測案例變成可回寫章節。案例表格提供線索，正文負責輸出訊號邊界與路由。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">4.C1</a></td>
          <td>FinTech 審計證據觀測</td>
          <td>把審計與證據鏈變成可觀測訊號</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/" data-link-title="Gaming：高峰流量下的訊號新鮮度與 Cardinality" data-link-desc="在高峰事件中控制訊號延遲與維度爆炸，維持告警與定位可信度。">4.C2</a></td>
          <td>Gaming 高峰訊號治理</td>
          <td>把高峰流量下訊號失真風險前移</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/" data-link-title="Healthcare：存取可追溯性與保留邊界" data-link-desc="在資料主權限制下，建立可追溯存取證據與分層保留策略。">4.C3</a></td>
          <td>Healthcare 存取可追溯性</td>
          <td>把資料主權場景的存取證據做成治理閉環</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/" data-link-title="4.C4 AWS：X-Ray 到 OpenTelemetry 轉換" data-link-desc="觀測儀表從 vendor-specific SDK 轉向 OpenTelemetry 的治理重點。">4.C4</a></td>
          <td>X-Ray 到 OTel 轉換</td>
          <td>把觀測遷移標準化成可分段執行流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/" data-link-title="4.C5 Google Cloud：Cloud Trace 導入 OTLP 入口" data-link-desc="觀測平台從專有入口擴展到 OTLP 標準通道的案例。">4.C5</a></td>
          <td>Cloud Trace OTLP 導入</td>
          <td>把資料通道標準化納入觀測平台治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/" data-link-title="4.C6 AWS：ADOT on EKS 管線遷移" data-link-desc="從分散式 agent 組合轉成 OpenTelemetry collector 管線治理。">4.C6</a></td>
          <td>ADOT on EKS 遷移</td>
          <td>把 collector/agent 管線轉換成集中治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/datadog-otel-migration-practice/" data-link-title="4.C7 Datadog：OTel 相容遷移實務" data-link-desc="APM 採集從專有代理轉向 OTel 相容模式的治理案例。">4.C7</a></td>
          <td>Datadog OTel 遷移實務</td>
          <td>把 APM 採集轉成 OTel-compatible 流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/" data-link-title="4.C8 Airbnb：Kubernetes 規模化下的觀測訊號治理" data-link-desc="叢集擴縮與工作負載變動如何回寫觀測模型。">4.C8</a></td>
          <td>Airbnb K8s 規模化訊號</td>
          <td>把叢集擴縮行為接回觀測與容量治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/" data-link-title="4.C9 反例：OTel 遷移後訊號漂移" data-link-desc="雙軌採集未對齊導致告警與 SLO 判讀失真。">4.C9</a></td>
          <td>反例：OTel 遷移訊號漂移</td>
          <td>雙軌採集未對齊導致告警與 SLO 判讀失真</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/" data-link-title="4.C10 對照：規模差異下的觀測遷移" data-link-desc="觀測遷移在不同規模團隊下的流程與風險差異。">4.C10</a></td>
          <td>對照：規模差異下觀測遷移</td>
          <td>不同規模團隊在觀測遷移的風險與流程差異</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/" data-link-title="4.C11 Uber：M3 大規模 Metrics 平台" data-link-desc="從散落的 Prometheus 實例到統一 metrics 平台，處理 cardinality 爆炸、長期 retention 與跨叢集查詢的規模化挑戰。">4.C11</a></td>
          <td>Uber M3 大規模 Metrics</td>
          <td>從散落的 Prometheus 到統一 metrics 平台</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/" data-link-title="4.C12 Cloudflare：內部觀測平台的三層能力" data-link-desc="全球 300&#43; edge 節點的觀測架構，把 monitoring、analytics 與 forensics 拆成三個獨立能力層。">4.C12</a></td>
          <td>Cloudflare 觀測三層能力</td>
          <td>monitoring / analytics / forensics 拆分</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/" data-link-title="4.C13 Discord：從儲存問題回推觀測缺口" data-link-desc="每次儲存遷移都暴露觀測盲區，把儲存成長問題重新框架為訊號設計問題。">4.C13</a></td>
          <td>Discord 儲存→觀測缺口</td>
          <td>每次遷移暴露觀測盲區的共同結構</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/cases/observability-cost-governance-at-scale/" data-link-title="4.C14 觀測平台成本治理：從帳單驚嚇到可預測成本" data-link-desc="觀測帳單持續超線性成長時，用 cost attribution、cardinality budget、log tiering 跟 adaptive sampling 建立可預測成本模型。">4.C14</a></td>
          <td>觀測成本治理</td>
          <td>attribution + cardinality budget + tiering</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>模組二案例正文</title><link>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/02-cache-redis/cases/</guid><description>&lt;p>這個資料夾的核心責任是把快取與 Redis 的轉換壓力寫成可回寫正文，而不是只列工具名稱。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cache-consistency-upgrade/" data-link-title="2.C1 Meta：Cache Consistency 升級" data-link-desc="快取 invalidation 一致性如何從常見錯誤演進到高可信治理。">2.C1&lt;/a>&lt;/td>
 &lt;td>Meta cache 一致性升級&lt;/td>
 &lt;td>把 invalidation 不一致問題轉成可觀測與可治理流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-mcrouter-global-cache-routing/" data-link-title="2.C2 Meta：mcrouter 與跨區快取路由" data-link-desc="快取從單點最佳化演進到分散式路由層的案例。">2.C2&lt;/a>&lt;/td>
 &lt;td>Meta mcrouter 快取路由&lt;/td>
 &lt;td>把單叢集快取演進到跨區路由與失效隔離&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-cache-serialization-migration/" data-link-title="2.C3 Shopify：快取序列化格式遷移" data-link-desc="快取 payload 從 Marshal 轉 MessagePack 的遷移策略。">2.C3&lt;/a>&lt;/td>
 &lt;td>Shopify 快取序列化遷移&lt;/td>
 &lt;td>把快取 payload 格式遷移做成雙軌相容與回退&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-cachelib-kangaroo-tiered-cache/" data-link-title="2.C4 Meta：CacheLib / Kangaroo 分層快取" data-link-desc="快取從 DRAM-only 轉向分層快取架構的實務案例。">2.C4&lt;/a>&lt;/td>
 &lt;td>Meta CacheLib 分層快取&lt;/td>
 &lt;td>把 DRAM-only 快取演進到記憶體/快閃分層架構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/shopify-write-through-cache-at-scale/" data-link-title="2.C5 Shopify：Write-through Cache 在高讀流量的實作" data-link-desc="read-heavy 服務如何轉向 write-through 快取模型。">2.C5&lt;/a>&lt;/td>
 &lt;td>Shopify write-through&lt;/td>
 &lt;td>把 read-heavy 路徑轉成寫入同步快取策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/netflix-evcache-global-cache-layer/" data-link-title="2.C6 Netflix：EVCache 全域快取層" data-link-desc="快取從本地層演進為跨區分散式能力的案例。">2.C6&lt;/a>&lt;/td>
 &lt;td>Netflix EVCache&lt;/td>
 &lt;td>把本地快取演進成跨區分散式快取層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/cloudflare-cache-reserve-tiered-storage/" data-link-title="2.C7 Cloudflare：Cache Reserve 分層儲存快取" data-link-desc="邊緣快取延伸到持久層以降低回源壓力的案例。">2.C7&lt;/a>&lt;/td>
 &lt;td>Cloudflare Cache Reserve&lt;/td>
 &lt;td>把邊緣快取延伸到持久層降低回源壓力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/meta-tao-social-graph-cache-evolution/" data-link-title="2.C8 Meta：TAO 社交圖快取演進" data-link-desc="社交圖查詢在規模化下如何把快取做成資料層能力。">2.C8&lt;/a>&lt;/td>
 &lt;td>Meta TAO&lt;/td>
 &lt;td>把 graph cache 演進成可擴展的一致性資料層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/failure-cache-stampede-rollout-regression/" data-link-title="2.C9 反例：快取切換引發 Stampede 回歸" data-link-desc="快取策略切換若缺乏保護，會導致回源壓力與錯誤率連鎖上升。">2.C9&lt;/a>&lt;/td>
 &lt;td>反例：快取切換失敗&lt;/td>
 &lt;td>快取策略切換若無防線會觸發 stampede 與回源雪崩&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cases/contrast-cache-strategy-by-scale/" data-link-title="2.C10 對照：規模差異下的快取策略" data-link-desc="同一快取策略在小中大型服務下會產生不同風險。">2.C10&lt;/a>&lt;/td>
 &lt;td>對照：規模差異下快取策略&lt;/td>
 &lt;td>小中大型服務用同一快取策略會造成不同失敗型態&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把快取與 Redis 的轉換壓力寫成可回寫正文，而不是只列工具名稱。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/meta-cache-consistency-upgrade/" data-link-title="2.C1 Meta：Cache Consistency 升級" data-link-desc="快取 invalidation 一致性如何從常見錯誤演進到高可信治理。">2.C1</a></td>
          <td>Meta cache 一致性升級</td>
          <td>把 invalidation 不一致問題轉成可觀測與可治理流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/meta-mcrouter-global-cache-routing/" data-link-title="2.C2 Meta：mcrouter 與跨區快取路由" data-link-desc="快取從單點最佳化演進到分散式路由層的案例。">2.C2</a></td>
          <td>Meta mcrouter 快取路由</td>
          <td>把單叢集快取演進到跨區路由與失效隔離</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/shopify-cache-serialization-migration/" data-link-title="2.C3 Shopify：快取序列化格式遷移" data-link-desc="快取 payload 從 Marshal 轉 MessagePack 的遷移策略。">2.C3</a></td>
          <td>Shopify 快取序列化遷移</td>
          <td>把快取 payload 格式遷移做成雙軌相容與回退</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/meta-cachelib-kangaroo-tiered-cache/" data-link-title="2.C4 Meta：CacheLib / Kangaroo 分層快取" data-link-desc="快取從 DRAM-only 轉向分層快取架構的實務案例。">2.C4</a></td>
          <td>Meta CacheLib 分層快取</td>
          <td>把 DRAM-only 快取演進到記憶體/快閃分層架構</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/shopify-write-through-cache-at-scale/" data-link-title="2.C5 Shopify：Write-through Cache 在高讀流量的實作" data-link-desc="read-heavy 服務如何轉向 write-through 快取模型。">2.C5</a></td>
          <td>Shopify write-through</td>
          <td>把 read-heavy 路徑轉成寫入同步快取策略</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/netflix-evcache-global-cache-layer/" data-link-title="2.C6 Netflix：EVCache 全域快取層" data-link-desc="快取從本地層演進為跨區分散式能力的案例。">2.C6</a></td>
          <td>Netflix EVCache</td>
          <td>把本地快取演進成跨區分散式快取層</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/cloudflare-cache-reserve-tiered-storage/" data-link-title="2.C7 Cloudflare：Cache Reserve 分層儲存快取" data-link-desc="邊緣快取延伸到持久層以降低回源壓力的案例。">2.C7</a></td>
          <td>Cloudflare Cache Reserve</td>
          <td>把邊緣快取延伸到持久層降低回源壓力</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/meta-tao-social-graph-cache-evolution/" data-link-title="2.C8 Meta：TAO 社交圖快取演進" data-link-desc="社交圖查詢在規模化下如何把快取做成資料層能力。">2.C8</a></td>
          <td>Meta TAO</td>
          <td>把 graph cache 演進成可擴展的一致性資料層</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/failure-cache-stampede-rollout-regression/" data-link-title="2.C9 反例：快取切換引發 Stampede 回歸" data-link-desc="快取策略切換若缺乏保護，會導致回源壓力與錯誤率連鎖上升。">2.C9</a></td>
          <td>反例：快取切換失敗</td>
          <td>快取策略切換若無防線會觸發 stampede 與回源雪崩</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/02-cache-redis/cases/contrast-cache-strategy-by-scale/" data-link-title="2.C10 對照：規模差異下的快取策略" data-link-desc="同一快取策略在小中大型服務下會產生不同風險。">2.C10</a></td>
          <td>對照：規模差異下快取策略</td>
          <td>小中大型服務用同一快取策略會造成不同失敗型態</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>模組三案例正文</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/</guid><description>&lt;p>這個資料夾的核心責任是把 broker、queue 與語義治理的轉換壓力落到可執行判讀、並提供各 vendor 的真實 production case 庫支撐撰寫。案例不是事後舉例、是寫作 finding 的 source — 章節該討論的議題從 case 反推、不是先寫章節再找案例填。&lt;/p>
&lt;h2 id="通用案例跨-vendor--反例--規模對照">通用案例（跨 vendor / 反例 / 規模對照）&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;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1&lt;/a>&lt;/td>
 &lt;td>Meta FOQS 全域遷移&lt;/td>
 &lt;td>區域佇列如何升級到 disaster-ready 架構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2&lt;/a>&lt;/td>
 &lt;td>VMware Kafka → MSK&lt;/td>
 &lt;td>自管 broker 轉 managed streaming 的治理重點&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3&lt;/a>&lt;/td>
 &lt;td>LinkedIn TopicGC&lt;/td>
 &lt;td>topic 生命週期治理如何影響叢集可靠性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/" data-link-title="3.C4 LinkedIn：Kafka 分層叢集治理" data-link-desc="Kafka 從單叢集走向 tiered clusters 的轉換案例。">3.C4&lt;/a>&lt;/td>
 &lt;td>LinkedIn Kafka 分層&lt;/td>
 &lt;td>把單叢集使用模式轉成分層叢集治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/" data-link-title="3.C5 Slack：Job Queue 演進到 Kafka &amp;#43; Redis" data-link-desc="背景工作通道在成長期如何從單一路徑演進成組合式架構。">3.C5&lt;/a>&lt;/td>
 &lt;td>Slack Job Queue&lt;/td>
 &lt;td>背景工作通道轉成 Kafka + Redis 組合&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6&lt;/a>&lt;/td>
 &lt;td>Uber Kafka 基礎設施&lt;/td>
 &lt;td>把事件平台演進成多租戶共享能力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/" data-link-title="3.C7 LinkedIn：Kafka 自動修復治理" data-link-desc="Kafka 維運從人工處置轉向自動修復的案例。">3.C7&lt;/a>&lt;/td>
 &lt;td>LinkedIn Self-healing Kafka&lt;/td>
 &lt;td>把手動維運轉成自動修復治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/" data-link-title="3.C8 Cloudflare：Queues 全球交付模型" data-link-desc="事件佇列服務在全球網路下的交付語義與治理案例。">3.C8&lt;/a>&lt;/td>
 &lt;td>Cloudflare Queues&lt;/td>
 &lt;td>把全球佇列傳遞模型轉成可治理交付路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9&lt;/a>&lt;/td>
 &lt;td>反例：語義切換失敗&lt;/td>
 &lt;td>at-least-once / exactly-once 語義誤配造成資料錯亂&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/" data-link-title="3.C10 對照：規模差異下的佇列模型" data-link-desc="同一 queue 模型在不同規模下的治理與失敗邊界差異。">3.C10&lt;/a>&lt;/td>
 &lt;td>對照：規模差異下佇列模型&lt;/td>
 &lt;td>同一佇列模型在不同規模下有不同治理與失敗邊界&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="kafka-案例">Kafka 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 Kafka 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/" data-link-title="3.C11 Pinterest：Kafka tiered storage broker-decoupled" data-link-desc="Pinterest 採 broker-decoupled tiered storage、把 ~200 TB/day 熱資料卸到 S3、broker 不再是熱路徑。">3.C11&lt;/a>&lt;/td>
 &lt;td>Pinterest Tiered Storage&lt;/td>
 &lt;td>Tiered storage&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/" data-link-title="3.C12 Pinterest：Shallow Mirror 優化 MirrorMaker" data-link-desc="Pinterest 跨 3 region MirrorMaker、原版解壓&amp;#43;重壓造成 CPU/memory 2-10x spike、改 RecordBatch 層淺迭代。">3.C12&lt;/a>&lt;/td>
 &lt;td>Pinterest Shallow Mirror&lt;/td>
 &lt;td>Cross-region MirrorMaker&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/" data-link-title="3.C13 Shopify：Debezium CDC over sharded MySQL" data-link-desc="Shopify 100&amp;#43; MySQL shard、150 Debezium connector、Black Friday 100K records/sec P99 &amp;lt; 10s。">3.C13&lt;/a>&lt;/td>
 &lt;td>Shopify Debezium CDC&lt;/td>
 &lt;td>Kafka Connect / CDC&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/" data-link-title="3.C14 Yelp：Schematizer 自建 Schema Registry" data-link-desc="Yelp data pipeline 強制所有 message 走 Avro、自建 Schematizer 做 schema evolution 與 topic 自動分配。">3.C14&lt;/a>&lt;/td>
 &lt;td>Yelp Schematizer&lt;/td>
 &lt;td>Schema Registry / Schema evolution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/" data-link-title="3.C15 Airbnb：Spark Streaming Kafka reader rebalance" data-link-desc="Airbnb logging pipeline 解 partition-task 1:1 造成的 data skew、catch-up 4 小時 lag 要再花 4 小時的反效率。">3.C15&lt;/a>&lt;/td>
 &lt;td>Airbnb Spark Streaming&lt;/td>
 &lt;td>Consumer 設計 / partition + consumer group&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16&lt;/a>&lt;/td>
 &lt;td>Robinhood Faust&lt;/td>
 &lt;td>跨語言 client / stream processing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/" data-link-title="3.C17 Walmart：Messaging Proxy Service 解 rebalance storm" data-link-desc="Walmart 每天 trillions of message、25K&amp;#43; consumer 在 K8s、partition-consumer 1:1 模型撞到擴張極限。">3.C17&lt;/a>&lt;/td>
 &lt;td>Walmart MPS&lt;/td>
 &lt;td>Rebalance storm / consumer lag / multi-tenant&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/" data-link-title="3.C18 Wix：Greyhound TLLSR 解 consumer 卡住" data-link-desc="Wix 2000&amp;#43; microservice 66B msg/day、自建 Greyhound 抽象、TLLSR 框架解 single-partition lag / poison pill / handler 卡住。">3.C18&lt;/a>&lt;/td>
 &lt;td>Wix Greyhound&lt;/td>
 &lt;td>Consumer lag / observability / poison message&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/" data-link-title="3.C19 Wix：Multi-cluster Kafka zero-downtime 遷移" data-link-desc="Wix metadata 從 5K topic 漲到 20K topic / 200K partition、controller startup 跟 broker stability 受壓垮、分多 cluster 解決。">3.C19&lt;/a>&lt;/td>
 &lt;td>Wix Multi-cluster&lt;/td>
 &lt;td>Topic 生命週期 / 分層叢集&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20&lt;/a>&lt;/td>
 &lt;td>Spotify 遷出 Kafka（反例）&lt;/td>
 &lt;td>Replication 失敗模式 / producer 可靠性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/" data-link-title="3.C21 Goldman Sachs：MSK 遷移 with MirrorMaker 2" data-link-desc="Goldman Sachs Global Investment Research 從 on-prem Kafka 遷到 MSK、用 MM2 同步 topic/ACL/offset、atomic cutover 7 小時完成。">3.C21&lt;/a>&lt;/td>
 &lt;td>Goldman Sachs MSK&lt;/td>
 &lt;td>Cross-region MirrorMaker / managed broker 遷移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&amp;#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22&lt;/a>&lt;/td>
 &lt;td>Trivago KEDA&lt;/td>
 &lt;td>Consumer lag / autoscaling&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="rabbitmq-案例">RabbitMQ 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 RabbitMQ 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &amp;#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23&lt;/a>&lt;/td>
 &lt;td>Bloomberg vhost 多租戶&lt;/td>
 &lt;td>多 vhost + 多租戶 / Erlang clustering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/" data-link-title="3.C24 SoundCloud：AMQP fan-out 音訊處理 pipeline" data-link-desc="SoundCloud 每秒 20-30K persistent message、不同處理類型分開隊列、各自獨立 scale。">3.C24&lt;/a>&lt;/td>
 &lt;td>SoundCloud fan-out 音訊&lt;/td>
 &lt;td>Prefetch + consumer 併發 / Streams&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/" data-link-title="3.C25 Indeed：Delay queue &amp;#43; DLQ 三層 escalation" data-link-desc="Indeed 每天 35M&amp;#43; 職缺、設計 Requeue → Delay queue → DLQ 三層 escalation 避開 head-of-line blocking。">3.C25&lt;/a>&lt;/td>
 &lt;td>Indeed Delay + DLQ&lt;/td>
 &lt;td>Dead-letter exchange / retry 策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &amp;#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26&lt;/a>&lt;/td>
 &lt;td>GoCardless Hutch service mesh&lt;/td>
 &lt;td>Exchange types / 多 vhost（反向）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/" data-link-title="3.C27 Zalando：RabbitMQ on AWS 自動化 master selection" data-link-desc="Zalando 用 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、跨版本升級用 federation 過渡。">3.C27&lt;/a>&lt;/td>
 &lt;td>Zalando AWS master selection&lt;/td>
 &lt;td>Erlang clustering / Federation / Operator&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/" data-link-title="3.C28 WeWork：Consistent hash exchange 保證帳戶順序" data-link-desc="WeWork 固定數量 queue &amp;#43; account ID hash 路由、每 queue 一個 worker &amp;#43; exclusive consumer 保 partition-level ordering。">3.C28&lt;/a>&lt;/td>
 &lt;td>WeWork consistent hash&lt;/td>
 &lt;td>Exchange types / partition-level ordering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/" data-link-title="3.C29 WeWork：Bunny &amp;#43; Puma 多執行緒 channel pool" data-link-desc="WeWork 從 Unicorn 切到 Puma 後遇 ConnectionClosedError、根因是 AMQP channel 跨執行緒共用、改用 connection_pool 管理。">3.C29&lt;/a>&lt;/td>
 &lt;td>WeWork Bunny channel pool&lt;/td>
 &lt;td>Prefetch + consumer 併發（client lib）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/" data-link-title="3.C30 Runtastic：Mirrored queue 網路負載瓶頸" data-link-desc="Runtastic 2020 lockdown 流量暴增、performance test 揭露 mirroring 邏輯把網路元件壓垮、調整 mirroring 配置消除瓶頸。">3.C30&lt;/a>&lt;/td>
 &lt;td>Runtastic mirrored queue 瓶頸&lt;/td>
 &lt;td>Mirrored queue → Quorum queue 遷移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/" data-link-title="3.C31 Mozilla Pulse：命名前綴 &amp;#43; ACL 取代 vhost 多租戶" data-link-desc="Mozilla Pulse 不用 vhost、改用權限 &amp;#43; 命名前綴 (exchange/{user}/*) 做隔離、CloudAMQP 託管、PulseGuardian 管使用者。">3.C31&lt;/a>&lt;/td>
 &lt;td>Mozilla Pulse naming isolation&lt;/td>
 &lt;td>多 vhost + 多租戶（反向：用 ACL + naming）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/" data-link-title="3.C32 LoyaltyLion：監控數千 RabbitMQ queue" data-link-desc="LoyaltyLion 跑數千 queue、用 rabbitmqctl &amp;#43; statsd 推 Datadog、揭露大規模 queue 拓樸下原生 plugin API 不夠用。">3.C32&lt;/a>&lt;/td>
 &lt;td>LoyaltyLion 監控數千 queue&lt;/td>
 &lt;td>監控觀測 / Operator&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/" data-link-title="3.C33 Wargaming：World of Tanks 戰後 dossier 解耦" data-link-desc="Wargaming WoT server 全 Linux、戰後 dossier 寫 RabbitMQ、portal 顯示統計而不增 game server load。">3.C33&lt;/a>&lt;/td>
 &lt;td>Wargaming game portal 解耦&lt;/td>
 &lt;td>Federation + Shovel / 多 vhost&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="nats-案例">NATS 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 NATS 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/" data-link-title="3.C34 Netlify：NATS 當全球 metrics/logs 統一資料平面" data-link-desc="Netlify 70K&amp;#43; 網站、10 億 PV/月、跨多雲、NATS 當 all-purpose data plane fan-out bus、超 RabbitMQ 評估。">3.C34&lt;/a>&lt;/td>
 &lt;td>Netlify 全球資料平面 fan-out&lt;/td>
 &lt;td>Core NATS vs JetStream / subject-based routing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/" data-link-title="3.C35 Form3：NATS JetStream 多雲低延遲支付" data-link-desc="Form3 服務 Tier-1 銀行、500ms SLA、SNS/SQS 吃 300ms 預算、改 NATS&amp;#43;JetStream 跨雲 6x 延遲改善。">3.C35&lt;/a>&lt;/td>
 &lt;td>Form3 多雲低延遲支付&lt;/td>
 &lt;td>Cluster + Supercluster + Leaf node / JetStream&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &amp;#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&amp;lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36&lt;/a>&lt;/td>
 &lt;td>Intelecy 工業 IoT&lt;/td>
 &lt;td>JetStream stream / Subject-based ACL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &amp;#43; JetStream &amp;#43; KV &amp;#43; Object Store。">3.C37&lt;/a>&lt;/td>
 &lt;td>MachineMetrics edge to cloud&lt;/td>
 &lt;td>Leaf node / KV + Object Store / 多租戶 ACL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/" data-link-title="3.C38 Clarifai：NATS Streaming ML 平台非同步任務" data-link-desc="Clarifai custom model 訓練、rolling deploy 掉訊息、改 NATS Streaming queue group、3 週遷移 1 服務、5 月 5 服務、每日 100k&amp;#43; 訊息 100% uptime。">3.C38&lt;/a>&lt;/td>
 &lt;td>Clarifai NATS Streaming ML&lt;/td>
 &lt;td>JetStream consumer 設計 / Queue groups&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/" data-link-title="3.C39 Choria：NATS 管 50 萬 server fleet" data-link-desc="Choria 替代 Puppet MCollective、NATS 單 binary 無 Zookeeper、4GB node 可達 50 萬 server、wildcard &amp;#43; queue group 做 scatter-gather RPC。">3.C39&lt;/a>&lt;/td>
 &lt;td>Choria fleet orchestration&lt;/td>
 &lt;td>Request/Reply / Queue groups / Supercluster&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/" data-link-title="3.C40 Resgate：WebSocket-to-NATS realtime API gateway" data-link-desc="Resgate 把 NATS subject 暴露成 REST &amp;#43; WebSocket、subject 階層當 schema、event 延遲 &amp;lt; 1ms、純 Core NATS。">3.C40&lt;/a>&lt;/td>
 &lt;td>Resgate WebSocket-to-NATS&lt;/td>
 &lt;td>Request/Reply / Subject ACL / Core NATS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/" data-link-title="3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus" data-link-desc="i-flow 每日 4 億筆 data operation、200&amp;#43; OT/IT connector、客戶含 Bosch / Sto / Lenze、NATS 當邊緣到 central 整合 bus。">3.C41&lt;/a>&lt;/td>
 &lt;td>i-flow OT/IT 整合&lt;/td>
 &lt;td>Cluster + Supercluster + Leaf node&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="redis-streams-案例">Redis Streams 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 Redis Streams 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/" data-link-title="3.C42 Bitso：Reliable Redis Streams 抽象 &amp;#43; 自建 DLQ" data-link-desc="Bitso 加密交易所、千 msg/sec/stream &amp;#43; 亞毫秒延遲、自建 Reliable Streams 封裝 PEL &amp;#43; retry &amp;#43; DLQ、idempotent processing。">3.C42&lt;/a>&lt;/td>
 &lt;td>Bitso Reliable Streams + DLQ&lt;/td>
 &lt;td>Consumer group + PEL / XCLAIM / Sentinel&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/" data-link-title="3.C43 Arcjet：Redis Streams 取代 Kafka 省 6 位數 $" data-link-desc="Arcjet security 平台、Kafka managed 6 位數 $/yr、用 Redis Streams 約 $1k/yr、自寫 Janitor 監控 retention。">3.C43&lt;/a>&lt;/td>
 &lt;td>Arcjet 取代 Kafka 省 6 位數 $&lt;/td>
 &lt;td>Retention / Memory 取捨&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/" data-link-title="3.C44 Harness：CD 微服務 async state transfer" data-link-desc="Harness CD 平台用 Redis Streams 解 brittle HTTP、揭露監控缺口 / MAXLEN truncation / head-of-line blocking 三類問題。">3.C44&lt;/a>&lt;/td>
 &lt;td>Harness CD async state transfer&lt;/td>
 &lt;td>Consumer group + PEL / XCLAIM / Memory&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/" data-link-title="3.C45 Klaxit：Rust &amp;#43; Redis Streams 處理 Heroku Logplex" data-link-desc="Klaxit carpool 用 Redis Streams 處理 Heroku Logplex 匯流、自動偵測修復平台 perf 問題、6 個月 production Rust。">3.C45&lt;/a>&lt;/td>
 &lt;td>Klaxit Rust + Heroku Logplex&lt;/td>
 &lt;td>XADD / XREADGROUP / Consumer group&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/" data-link-title="3.C46 Learning.com：Redis 事件源退場（反例）" data-link-desc="Learning.com 把 microservice event store 放 Redis、1 年累積 GB/週、AOF&amp;#43;EBS 變 latency 痛點、退到 PostgreSQL。">3.C46&lt;/a>&lt;/td>
 &lt;td>Learning.com 退場（反例）&lt;/td>
 &lt;td>Memory + retention / Sentinel 可靠性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/" data-link-title="3.C47 PHP 微服務：Redis Streams &amp;#43; S3 hybrid storage" data-link-desc="PHP 雙微服務通訊、Kafka 在 PHP 生態工具薄弱、用 Redis Streams &amp;#43; payload compression &amp;#43; S3 hybrid 處理大訊息。">3.C47&lt;/a>&lt;/td>
 &lt;td>PHP 微服務 + S3 hybrid&lt;/td>
 &lt;td>XADD/XREAD / Retention / Memory&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="aws-sqs-案例">AWS SQS 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 SQS 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/" data-link-title="3.C48 Airbnb Dynein：SQS 分散式延遲任務排程" data-link-desc="Airbnb 用 SQS at-least-once &amp;#43; DLQ 取代 Resque 單 Redis 限制、每 scheduler 1000 QPS、SQS wrap DynamoDB 處理 &amp;gt; 15 分鐘 delay。">3.C48&lt;/a>&lt;/td>
 &lt;td>Airbnb Dynein 延遲任務&lt;/td>
 &lt;td>Standard vs FIFO / DLQ 設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/" data-link-title="3.C49 Airbnb Inspekt：Visibility timeout 當 retry budget" data-link-desc="Airbnb Inspekt 隱私掃描器、scanner pull message、visibility timeout 自然觸發重現、用重現次數當 retry budget。">3.C49&lt;/a>&lt;/td>
 &lt;td>Airbnb Inspekt visibility timeout&lt;/td>
 &lt;td>Visibility timeout + in-flight&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/" data-link-title="3.C50 Capital One：Visibility timeout 設計與 Lambda event source" data-link-desc="Capital One tech blog 講 SQS &amp;#43; Lambda：visibility timeout 應略高於最大處理時間、Lambda 初 5 個 long polling、可擴 60/min。">3.C50&lt;/a>&lt;/td>
 &lt;td>Capital One visibility timeout&lt;/td>
 &lt;td>Visibility timeout / SQS + Lambda&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &amp;#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51&lt;/a>&lt;/td>
 &lt;td>Atlassian JiRT Kinesis + SQS&lt;/td>
 &lt;td>Standard vs FIFO / fan-out subscription&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/" data-link-title="3.C52 Nielsen：Spark on EKS 雙 SQS 工作流" data-link-desc="Nielsen 每日 25TB / 30B event、work queue &amp;#43; completion queue 雙 SQS、queue depth autoscale EKS pod。">3.C52&lt;/a>&lt;/td>
 &lt;td>Nielsen Spark on EKS 雙 SQS&lt;/td>
 &lt;td>CloudWatch metric / autoscaling&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/" data-link-title="3.C53 FINRA：S3 → SQS notification 大檔上傳" data-link-desc="FINRA 金融監管、broker 上傳大檔、S3 → SQS notification → LFS、KMS &amp;#43; bucket policy &amp;#43; queue policy 三層稽核。">3.C53&lt;/a>&lt;/td>
 &lt;td>FINRA S3 → SQS 合規&lt;/td>
 &lt;td>SQS + Lambda / IAM 多層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/" data-link-title="3.C54 Twitch EventSub：SNS&amp;#43;SQS fan-out 給第三方" data-link-desc="Twitch Event Bus ~1660 events/sec 進 SNS、EventSub 用 SQS 接收 &amp;#43; Dispatcher fan-out 給訂閱者。">3.C54&lt;/a>&lt;/td>
 &lt;td>Twitch EventSub SNS+SQS&lt;/td>
 &lt;td>Standard queue / SNS-SQS fan-out&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/" data-link-title="3.C55 SmugMug：SQS 驅動可重放搜尋管線" data-link-desc="SmugMug 用 SQS 兩種模式：DynamoDB scan-segment 平行 backfill &amp;#43; production query 鏡像 replay 到 replica。">3.C55&lt;/a>&lt;/td>
 &lt;td>SmugMug 搜尋管線 backfill&lt;/td>
 &lt;td>Standard queue / Long polling / Lambda&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &amp;#43; retention &amp;#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56&lt;/a>&lt;/td>
 &lt;td>PostNL EBE 完整 DLQ + redrive&lt;/td>
 &lt;td>DLQ 設計 / CloudWatch alarm / Cost&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/" data-link-title="3.C57 Lob：自家 fork @lob/sqs-consumer 修 FIFO bug" data-link-desc="Lob 原用 bbc/sqs-consumer 鎖 SDK v2、fork 出 @lob/sqs-consumer 支援 SDK v3 &amp;#43; TypeScript &amp;#43; 修 FIFO bug。">3.C57&lt;/a>&lt;/td>
 &lt;td>Lob @lob/sqs-consumer&lt;/td>
 &lt;td>Standard vs FIFO / Client library&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58&lt;/a>&lt;/td>
 &lt;td>Twilio SQS 緩衝 webhook&lt;/td>
 &lt;td>Long polling / Standard vs FIFO&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/" data-link-title="3.C59 Rapid7：SQS 100 億 message/day 規模" data-link-desc="Rapid7 公開引述：SQS 撐 10s of billions of messages per day、是架構關鍵元件、scale 量級的具體參考。">3.C59&lt;/a>&lt;/td>
 &lt;td>Rapid7 100 億 msg/day 規模&lt;/td>
 &lt;td>Cost 模型 / Standard queue&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="google-pubsub-案例">Google Pub/Sub 案例&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>公司 / 主題&lt;/th>
 &lt;th>對應 Pub/Sub 大綱章節&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/" data-link-title="3.C60 Spotify：Event Delivery 從 Kafka 遷到 Pub/Sub" data-link-desc="Spotify 全球 event delivery 從 Kafka 遷到 Pub/Sub、~2500 VM、Q1 2019 8M events/s、350TB/day raw、自建 dedup。">3.C60&lt;/a>&lt;/td>
 &lt;td>Spotify Event Delivery 遷入&lt;/td>
 &lt;td>Pub/Sub vs Lite / Push vs Pull / Ack deadline&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/" data-link-title="3.C61 Spotify：Autoscaling Pub/Sub consumer 反效果" data-link-desc="Spotify 下游失敗時 consumer 不 ack 仍耗 CPU、autoscaling 越拉越高、解法是 exponential backoff 抑制 CPU。">3.C61&lt;/a>&lt;/td>
 &lt;td>Spotify Autoscaling 反效果&lt;/td>
 &lt;td>Ack deadline / autoscaling signal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/" data-link-title="3.C62 Spotify：Pub/Sub → GCS reliable export" data-link-desc="Spotify 用 Oldest Unacknowledged Message metric 判斷 hourly bucket 何時可安全關閉、ack 綁定下游 commit。">3.C62&lt;/a>&lt;/td>
 &lt;td>Spotify reliable GCS export&lt;/td>
 &lt;td>Ack deadline / Cloud Storage subscription&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/" data-link-title="3.C63 Mercari Actionable History：ack deadline 是 batch-level" data-link-desc="Merpay 支付流水帳用 Pub/Sub、ack deadline 是整批 batch 而非單訊息、acked 訊息會跟同批 expired 一起 redeliver。">3.C63&lt;/a>&lt;/td>
 &lt;td>Mercari ack deadline batch-level&lt;/td>
 &lt;td>Ack deadline / Push vs Pull / Ordering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/" data-link-title="3.C64 Mercari Item Feed：DLT 防 poison message 阻塞" data-link-desc="Mercari 商品 feed 同步、ack 整批 / nack 重送、重試多次仍失敗送 DLT、topic 同時當 load-leveling buffer。">3.C64&lt;/a>&lt;/td>
 &lt;td>Mercari Item Feed DLT&lt;/td>
 &lt;td>Dead-letter topic / Push vs Pull&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/" data-link-title="3.C65 Mercari LINE：Pull subscription 對齊外部 RPS" data-link-desc="Mercari LINE webhook 轉 Pub/Sub、worker pull subscription 精確控制 RPS、應 LINE API 限制。">3.C65&lt;/a>&lt;/td>
 &lt;td>Mercari LINE 對齊外部 RPS&lt;/td>
 &lt;td>Push vs Pull subscription&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/" data-link-title="3.C66 Mercari B2C：自建 PubSub gRPC Pusher" data-link-desc="Mercari 全球商品同步、原生 HTTP push 在「長 job &amp;#43; 高吞吐 &amp;#43; 動態 RPS」場景受限、自建 gRPC 版 push。">3.C66&lt;/a>&lt;/td>
 &lt;td>Mercari B2C 自建 gRPC pusher&lt;/td>
 &lt;td>Push vs Pull / Ordering 應用層處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67&lt;/a>&lt;/td>
 &lt;td>Niantic Pokémon GO telemetry&lt;/td>
 &lt;td>BigQuery subscription（pattern 對照）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &amp;#43; Dataflow &amp;#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &amp;lt; 100ms、BigQuery 並行存 raw recovery。">3.C68&lt;/a>&lt;/td>
 &lt;td>Wix clickstream + Dataflow + BQ&lt;/td>
 &lt;td>BigQuery subscription / Push vs Pull&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/" data-link-title="3.C69 Twitter Ad Engagement：把 stream 切成多 topic 做 partition" data-link-desc="Twitter 把 80K msg/s stream 切成 6 個 topic 做 partition、Avro schema、Beam/Dataflow → Bigtable/BQ。">3.C69&lt;/a>&lt;/td>
 &lt;td>Twitter Ad Engagement topic 切分&lt;/td>
 &lt;td>Schema enforcement / Ordering key&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例覆蓋缺口待補">案例覆蓋缺口（待補）&lt;/h2>
&lt;p>下列大綱章節在本案例庫中&lt;strong>公開 customer-side case 偏弱或缺&lt;/strong>、撰寫正文時要明示「以下分析依官方文件 / KIP / 通用模式推導、非 case-driven」：&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把 broker、queue 與語義治理的轉換壓力落到可執行判讀、並提供各 vendor 的真實 production case 庫支撐撰寫。案例不是事後舉例、是寫作 finding 的 source — 章節該討論的議題從 case 反推、不是先寫章節再找案例填。</p>
<h2 id="通用案例跨-vendor--反例--規模對照">通用案例（跨 vendor / 反例 / 規模對照）</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1</a></td>
          <td>Meta FOQS 全域遷移</td>
          <td>區域佇列如何升級到 disaster-ready 架構</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2</a></td>
          <td>VMware Kafka → MSK</td>
          <td>自管 broker 轉 managed streaming 的治理重點</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3</a></td>
          <td>LinkedIn TopicGC</td>
          <td>topic 生命週期治理如何影響叢集可靠性</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/" data-link-title="3.C4 LinkedIn：Kafka 分層叢集治理" data-link-desc="Kafka 從單叢集走向 tiered clusters 的轉換案例。">3.C4</a></td>
          <td>LinkedIn Kafka 分層</td>
          <td>把單叢集使用模式轉成分層叢集治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/" data-link-title="3.C5 Slack：Job Queue 演進到 Kafka &#43; Redis" data-link-desc="背景工作通道在成長期如何從單一路徑演進成組合式架構。">3.C5</a></td>
          <td>Slack Job Queue</td>
          <td>背景工作通道轉成 Kafka + Redis 組合</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6</a></td>
          <td>Uber Kafka 基礎設施</td>
          <td>把事件平台演進成多租戶共享能力</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/" data-link-title="3.C7 LinkedIn：Kafka 自動修復治理" data-link-desc="Kafka 維運從人工處置轉向自動修復的案例。">3.C7</a></td>
          <td>LinkedIn Self-healing Kafka</td>
          <td>把手動維運轉成自動修復治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/" data-link-title="3.C8 Cloudflare：Queues 全球交付模型" data-link-desc="事件佇列服務在全球網路下的交付語義與治理案例。">3.C8</a></td>
          <td>Cloudflare Queues</td>
          <td>把全球佇列傳遞模型轉成可治理交付路徑</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9</a></td>
          <td>反例：語義切換失敗</td>
          <td>at-least-once / exactly-once 語義誤配造成資料錯亂</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/" data-link-title="3.C10 對照：規模差異下的佇列模型" data-link-desc="同一 queue 模型在不同規模下的治理與失敗邊界差異。">3.C10</a></td>
          <td>對照：規模差異下佇列模型</td>
          <td>同一佇列模型在不同規模下有不同治理與失敗邊界</td>
      </tr>
  </tbody>
</table>
<h2 id="kafka-案例">Kafka 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 Kafka 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/" data-link-title="3.C11 Pinterest：Kafka tiered storage broker-decoupled" data-link-desc="Pinterest 採 broker-decoupled tiered storage、把 ~200 TB/day 熱資料卸到 S3、broker 不再是熱路徑。">3.C11</a></td>
          <td>Pinterest Tiered Storage</td>
          <td>Tiered storage</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/" data-link-title="3.C12 Pinterest：Shallow Mirror 優化 MirrorMaker" data-link-desc="Pinterest 跨 3 region MirrorMaker、原版解壓&#43;重壓造成 CPU/memory 2-10x spike、改 RecordBatch 層淺迭代。">3.C12</a></td>
          <td>Pinterest Shallow Mirror</td>
          <td>Cross-region MirrorMaker</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/" data-link-title="3.C13 Shopify：Debezium CDC over sharded MySQL" data-link-desc="Shopify 100&#43; MySQL shard、150 Debezium connector、Black Friday 100K records/sec P99 &lt; 10s。">3.C13</a></td>
          <td>Shopify Debezium CDC</td>
          <td>Kafka Connect / CDC</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/" data-link-title="3.C14 Yelp：Schematizer 自建 Schema Registry" data-link-desc="Yelp data pipeline 強制所有 message 走 Avro、自建 Schematizer 做 schema evolution 與 topic 自動分配。">3.C14</a></td>
          <td>Yelp Schematizer</td>
          <td>Schema Registry / Schema evolution</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/" data-link-title="3.C15 Airbnb：Spark Streaming Kafka reader rebalance" data-link-desc="Airbnb logging pipeline 解 partition-task 1:1 造成的 data skew、catch-up 4 小時 lag 要再花 4 小時的反效率。">3.C15</a></td>
          <td>Airbnb Spark Streaming</td>
          <td>Consumer 設計 / partition + consumer group</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16</a></td>
          <td>Robinhood Faust</td>
          <td>跨語言 client / stream processing</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/" data-link-title="3.C17 Walmart：Messaging Proxy Service 解 rebalance storm" data-link-desc="Walmart 每天 trillions of message、25K&#43; consumer 在 K8s、partition-consumer 1:1 模型撞到擴張極限。">3.C17</a></td>
          <td>Walmart MPS</td>
          <td>Rebalance storm / consumer lag / multi-tenant</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/" data-link-title="3.C18 Wix：Greyhound TLLSR 解 consumer 卡住" data-link-desc="Wix 2000&#43; microservice 66B msg/day、自建 Greyhound 抽象、TLLSR 框架解 single-partition lag / poison pill / handler 卡住。">3.C18</a></td>
          <td>Wix Greyhound</td>
          <td>Consumer lag / observability / poison message</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/" data-link-title="3.C19 Wix：Multi-cluster Kafka zero-downtime 遷移" data-link-desc="Wix metadata 從 5K topic 漲到 20K topic / 200K partition、controller startup 跟 broker stability 受壓垮、分多 cluster 解決。">3.C19</a></td>
          <td>Wix Multi-cluster</td>
          <td>Topic 生命週期 / 分層叢集</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20</a></td>
          <td>Spotify 遷出 Kafka（反例）</td>
          <td>Replication 失敗模式 / producer 可靠性</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/" data-link-title="3.C21 Goldman Sachs：MSK 遷移 with MirrorMaker 2" data-link-desc="Goldman Sachs Global Investment Research 從 on-prem Kafka 遷到 MSK、用 MM2 同步 topic/ACL/offset、atomic cutover 7 小時完成。">3.C21</a></td>
          <td>Goldman Sachs MSK</td>
          <td>Cross-region MirrorMaker / managed broker 遷移</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22</a></td>
          <td>Trivago KEDA</td>
          <td>Consumer lag / autoscaling</td>
      </tr>
  </tbody>
</table>
<h2 id="rabbitmq-案例">RabbitMQ 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 RabbitMQ 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23</a></td>
          <td>Bloomberg vhost 多租戶</td>
          <td>多 vhost + 多租戶 / Erlang clustering</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/" data-link-title="3.C24 SoundCloud：AMQP fan-out 音訊處理 pipeline" data-link-desc="SoundCloud 每秒 20-30K persistent message、不同處理類型分開隊列、各自獨立 scale。">3.C24</a></td>
          <td>SoundCloud fan-out 音訊</td>
          <td>Prefetch + consumer 併發 / Streams</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/" data-link-title="3.C25 Indeed：Delay queue &#43; DLQ 三層 escalation" data-link-desc="Indeed 每天 35M&#43; 職缺、設計 Requeue → Delay queue → DLQ 三層 escalation 避開 head-of-line blocking。">3.C25</a></td>
          <td>Indeed Delay + DLQ</td>
          <td>Dead-letter exchange / retry 策略</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26</a></td>
          <td>GoCardless Hutch service mesh</td>
          <td>Exchange types / 多 vhost（反向）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/" data-link-title="3.C27 Zalando：RabbitMQ on AWS 自動化 master selection" data-link-desc="Zalando 用 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、跨版本升級用 federation 過渡。">3.C27</a></td>
          <td>Zalando AWS master selection</td>
          <td>Erlang clustering / Federation / Operator</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/" data-link-title="3.C28 WeWork：Consistent hash exchange 保證帳戶順序" data-link-desc="WeWork 固定數量 queue &#43; account ID hash 路由、每 queue 一個 worker &#43; exclusive consumer 保 partition-level ordering。">3.C28</a></td>
          <td>WeWork consistent hash</td>
          <td>Exchange types / partition-level ordering</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/" data-link-title="3.C29 WeWork：Bunny &#43; Puma 多執行緒 channel pool" data-link-desc="WeWork 從 Unicorn 切到 Puma 後遇 ConnectionClosedError、根因是 AMQP channel 跨執行緒共用、改用 connection_pool 管理。">3.C29</a></td>
          <td>WeWork Bunny channel pool</td>
          <td>Prefetch + consumer 併發（client lib）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/" data-link-title="3.C30 Runtastic：Mirrored queue 網路負載瓶頸" data-link-desc="Runtastic 2020 lockdown 流量暴增、performance test 揭露 mirroring 邏輯把網路元件壓垮、調整 mirroring 配置消除瓶頸。">3.C30</a></td>
          <td>Runtastic mirrored queue 瓶頸</td>
          <td>Mirrored queue → Quorum queue 遷移</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/" data-link-title="3.C31 Mozilla Pulse：命名前綴 &#43; ACL 取代 vhost 多租戶" data-link-desc="Mozilla Pulse 不用 vhost、改用權限 &#43; 命名前綴 (exchange/{user}/*) 做隔離、CloudAMQP 託管、PulseGuardian 管使用者。">3.C31</a></td>
          <td>Mozilla Pulse naming isolation</td>
          <td>多 vhost + 多租戶（反向：用 ACL + naming）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/" data-link-title="3.C32 LoyaltyLion：監控數千 RabbitMQ queue" data-link-desc="LoyaltyLion 跑數千 queue、用 rabbitmqctl &#43; statsd 推 Datadog、揭露大規模 queue 拓樸下原生 plugin API 不夠用。">3.C32</a></td>
          <td>LoyaltyLion 監控數千 queue</td>
          <td>監控觀測 / Operator</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/" data-link-title="3.C33 Wargaming：World of Tanks 戰後 dossier 解耦" data-link-desc="Wargaming WoT server 全 Linux、戰後 dossier 寫 RabbitMQ、portal 顯示統計而不增 game server load。">3.C33</a></td>
          <td>Wargaming game portal 解耦</td>
          <td>Federation + Shovel / 多 vhost</td>
      </tr>
  </tbody>
</table>
<h2 id="nats-案例">NATS 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 NATS 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/" data-link-title="3.C34 Netlify：NATS 當全球 metrics/logs 統一資料平面" data-link-desc="Netlify 70K&#43; 網站、10 億 PV/月、跨多雲、NATS 當 all-purpose data plane fan-out bus、超 RabbitMQ 評估。">3.C34</a></td>
          <td>Netlify 全球資料平面 fan-out</td>
          <td>Core NATS vs JetStream / subject-based routing</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/" data-link-title="3.C35 Form3：NATS JetStream 多雲低延遲支付" data-link-desc="Form3 服務 Tier-1 銀行、500ms SLA、SNS/SQS 吃 300ms 預算、改 NATS&#43;JetStream 跨雲 6x 延遲改善。">3.C35</a></td>
          <td>Form3 多雲低延遲支付</td>
          <td>Cluster + Supercluster + Leaf node / JetStream</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36</a></td>
          <td>Intelecy 工業 IoT</td>
          <td>JetStream stream / Subject-based ACL</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37</a></td>
          <td>MachineMetrics edge to cloud</td>
          <td>Leaf node / KV + Object Store / 多租戶 ACL</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/" data-link-title="3.C38 Clarifai：NATS Streaming ML 平台非同步任務" data-link-desc="Clarifai custom model 訓練、rolling deploy 掉訊息、改 NATS Streaming queue group、3 週遷移 1 服務、5 月 5 服務、每日 100k&#43; 訊息 100% uptime。">3.C38</a></td>
          <td>Clarifai NATS Streaming ML</td>
          <td>JetStream consumer 設計 / Queue groups</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/" data-link-title="3.C39 Choria：NATS 管 50 萬 server fleet" data-link-desc="Choria 替代 Puppet MCollective、NATS 單 binary 無 Zookeeper、4GB node 可達 50 萬 server、wildcard &#43; queue group 做 scatter-gather RPC。">3.C39</a></td>
          <td>Choria fleet orchestration</td>
          <td>Request/Reply / Queue groups / Supercluster</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/" data-link-title="3.C40 Resgate：WebSocket-to-NATS realtime API gateway" data-link-desc="Resgate 把 NATS subject 暴露成 REST &#43; WebSocket、subject 階層當 schema、event 延遲 &lt; 1ms、純 Core NATS。">3.C40</a></td>
          <td>Resgate WebSocket-to-NATS</td>
          <td>Request/Reply / Subject ACL / Core NATS</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/" data-link-title="3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus" data-link-desc="i-flow 每日 4 億筆 data operation、200&#43; OT/IT connector、客戶含 Bosch / Sto / Lenze、NATS 當邊緣到 central 整合 bus。">3.C41</a></td>
          <td>i-flow OT/IT 整合</td>
          <td>Cluster + Supercluster + Leaf node</td>
      </tr>
  </tbody>
</table>
<h2 id="redis-streams-案例">Redis Streams 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 Redis Streams 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/" data-link-title="3.C42 Bitso：Reliable Redis Streams 抽象 &#43; 自建 DLQ" data-link-desc="Bitso 加密交易所、千 msg/sec/stream &#43; 亞毫秒延遲、自建 Reliable Streams 封裝 PEL &#43; retry &#43; DLQ、idempotent processing。">3.C42</a></td>
          <td>Bitso Reliable Streams + DLQ</td>
          <td>Consumer group + PEL / XCLAIM / Sentinel</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/" data-link-title="3.C43 Arcjet：Redis Streams 取代 Kafka 省 6 位數 $" data-link-desc="Arcjet security 平台、Kafka managed 6 位數 $/yr、用 Redis Streams 約 $1k/yr、自寫 Janitor 監控 retention。">3.C43</a></td>
          <td>Arcjet 取代 Kafka 省 6 位數 $</td>
          <td>Retention / Memory 取捨</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/" data-link-title="3.C44 Harness：CD 微服務 async state transfer" data-link-desc="Harness CD 平台用 Redis Streams 解 brittle HTTP、揭露監控缺口 / MAXLEN truncation / head-of-line blocking 三類問題。">3.C44</a></td>
          <td>Harness CD async state transfer</td>
          <td>Consumer group + PEL / XCLAIM / Memory</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/" data-link-title="3.C45 Klaxit：Rust &#43; Redis Streams 處理 Heroku Logplex" data-link-desc="Klaxit carpool 用 Redis Streams 處理 Heroku Logplex 匯流、自動偵測修復平台 perf 問題、6 個月 production Rust。">3.C45</a></td>
          <td>Klaxit Rust + Heroku Logplex</td>
          <td>XADD / XREADGROUP / Consumer group</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/" data-link-title="3.C46 Learning.com：Redis 事件源退場（反例）" data-link-desc="Learning.com 把 microservice event store 放 Redis、1 年累積 GB/週、AOF&#43;EBS 變 latency 痛點、退到 PostgreSQL。">3.C46</a></td>
          <td>Learning.com 退場（反例）</td>
          <td>Memory + retention / Sentinel 可靠性</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/" data-link-title="3.C47 PHP 微服務：Redis Streams &#43; S3 hybrid storage" data-link-desc="PHP 雙微服務通訊、Kafka 在 PHP 生態工具薄弱、用 Redis Streams &#43; payload compression &#43; S3 hybrid 處理大訊息。">3.C47</a></td>
          <td>PHP 微服務 + S3 hybrid</td>
          <td>XADD/XREAD / Retention / Memory</td>
      </tr>
  </tbody>
</table>
<h2 id="aws-sqs-案例">AWS SQS 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 SQS 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/" data-link-title="3.C48 Airbnb Dynein：SQS 分散式延遲任務排程" data-link-desc="Airbnb 用 SQS at-least-once &#43; DLQ 取代 Resque 單 Redis 限制、每 scheduler 1000 QPS、SQS wrap DynamoDB 處理 &gt; 15 分鐘 delay。">3.C48</a></td>
          <td>Airbnb Dynein 延遲任務</td>
          <td>Standard vs FIFO / DLQ 設計</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/" data-link-title="3.C49 Airbnb Inspekt：Visibility timeout 當 retry budget" data-link-desc="Airbnb Inspekt 隱私掃描器、scanner pull message、visibility timeout 自然觸發重現、用重現次數當 retry budget。">3.C49</a></td>
          <td>Airbnb Inspekt visibility timeout</td>
          <td>Visibility timeout + in-flight</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/" data-link-title="3.C50 Capital One：Visibility timeout 設計與 Lambda event source" data-link-desc="Capital One tech blog 講 SQS &#43; Lambda：visibility timeout 應略高於最大處理時間、Lambda 初 5 個 long polling、可擴 60/min。">3.C50</a></td>
          <td>Capital One visibility timeout</td>
          <td>Visibility timeout / SQS + Lambda</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51</a></td>
          <td>Atlassian JiRT Kinesis + SQS</td>
          <td>Standard vs FIFO / fan-out subscription</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/" data-link-title="3.C52 Nielsen：Spark on EKS 雙 SQS 工作流" data-link-desc="Nielsen 每日 25TB / 30B event、work queue &#43; completion queue 雙 SQS、queue depth autoscale EKS pod。">3.C52</a></td>
          <td>Nielsen Spark on EKS 雙 SQS</td>
          <td>CloudWatch metric / autoscaling</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/" data-link-title="3.C53 FINRA：S3 → SQS notification 大檔上傳" data-link-desc="FINRA 金融監管、broker 上傳大檔、S3 → SQS notification → LFS、KMS &#43; bucket policy &#43; queue policy 三層稽核。">3.C53</a></td>
          <td>FINRA S3 → SQS 合規</td>
          <td>SQS + Lambda / IAM 多層</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/" data-link-title="3.C54 Twitch EventSub：SNS&#43;SQS fan-out 給第三方" data-link-desc="Twitch Event Bus ~1660 events/sec 進 SNS、EventSub 用 SQS 接收 &#43; Dispatcher fan-out 給訂閱者。">3.C54</a></td>
          <td>Twitch EventSub SNS+SQS</td>
          <td>Standard queue / SNS-SQS fan-out</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/" data-link-title="3.C55 SmugMug：SQS 驅動可重放搜尋管線" data-link-desc="SmugMug 用 SQS 兩種模式：DynamoDB scan-segment 平行 backfill &#43; production query 鏡像 replay 到 replica。">3.C55</a></td>
          <td>SmugMug 搜尋管線 backfill</td>
          <td>Standard queue / Long polling / Lambda</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &#43; retention &#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56</a></td>
          <td>PostNL EBE 完整 DLQ + redrive</td>
          <td>DLQ 設計 / CloudWatch alarm / Cost</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/" data-link-title="3.C57 Lob：自家 fork @lob/sqs-consumer 修 FIFO bug" data-link-desc="Lob 原用 bbc/sqs-consumer 鎖 SDK v2、fork 出 @lob/sqs-consumer 支援 SDK v3 &#43; TypeScript &#43; 修 FIFO bug。">3.C57</a></td>
          <td>Lob @lob/sqs-consumer</td>
          <td>Standard vs FIFO / Client library</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58</a></td>
          <td>Twilio SQS 緩衝 webhook</td>
          <td>Long polling / Standard vs FIFO</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/" data-link-title="3.C59 Rapid7：SQS 100 億 message/day 規模" data-link-desc="Rapid7 公開引述：SQS 撐 10s of billions of messages per day、是架構關鍵元件、scale 量級的具體參考。">3.C59</a></td>
          <td>Rapid7 100 億 msg/day 規模</td>
          <td>Cost 模型 / Standard queue</td>
      </tr>
  </tbody>
</table>
<h2 id="google-pubsub-案例">Google Pub/Sub 案例</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>公司 / 主題</th>
          <th>對應 Pub/Sub 大綱章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/" data-link-title="3.C60 Spotify：Event Delivery 從 Kafka 遷到 Pub/Sub" data-link-desc="Spotify 全球 event delivery 從 Kafka 遷到 Pub/Sub、~2500 VM、Q1 2019 8M events/s、350TB/day raw、自建 dedup。">3.C60</a></td>
          <td>Spotify Event Delivery 遷入</td>
          <td>Pub/Sub vs Lite / Push vs Pull / Ack deadline</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/" data-link-title="3.C61 Spotify：Autoscaling Pub/Sub consumer 反效果" data-link-desc="Spotify 下游失敗時 consumer 不 ack 仍耗 CPU、autoscaling 越拉越高、解法是 exponential backoff 抑制 CPU。">3.C61</a></td>
          <td>Spotify Autoscaling 反效果</td>
          <td>Ack deadline / autoscaling signal</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/" data-link-title="3.C62 Spotify：Pub/Sub → GCS reliable export" data-link-desc="Spotify 用 Oldest Unacknowledged Message metric 判斷 hourly bucket 何時可安全關閉、ack 綁定下游 commit。">3.C62</a></td>
          <td>Spotify reliable GCS export</td>
          <td>Ack deadline / Cloud Storage subscription</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/" data-link-title="3.C63 Mercari Actionable History：ack deadline 是 batch-level" data-link-desc="Merpay 支付流水帳用 Pub/Sub、ack deadline 是整批 batch 而非單訊息、acked 訊息會跟同批 expired 一起 redeliver。">3.C63</a></td>
          <td>Mercari ack deadline batch-level</td>
          <td>Ack deadline / Push vs Pull / Ordering</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/" data-link-title="3.C64 Mercari Item Feed：DLT 防 poison message 阻塞" data-link-desc="Mercari 商品 feed 同步、ack 整批 / nack 重送、重試多次仍失敗送 DLT、topic 同時當 load-leveling buffer。">3.C64</a></td>
          <td>Mercari Item Feed DLT</td>
          <td>Dead-letter topic / Push vs Pull</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/" data-link-title="3.C65 Mercari LINE：Pull subscription 對齊外部 RPS" data-link-desc="Mercari LINE webhook 轉 Pub/Sub、worker pull subscription 精確控制 RPS、應 LINE API 限制。">3.C65</a></td>
          <td>Mercari LINE 對齊外部 RPS</td>
          <td>Push vs Pull subscription</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/" data-link-title="3.C66 Mercari B2C：自建 PubSub gRPC Pusher" data-link-desc="Mercari 全球商品同步、原生 HTTP push 在「長 job &#43; 高吞吐 &#43; 動態 RPS」場景受限、自建 gRPC 版 push。">3.C66</a></td>
          <td>Mercari B2C 自建 gRPC pusher</td>
          <td>Push vs Pull / Ordering 應用層處理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67</a></td>
          <td>Niantic Pokémon GO telemetry</td>
          <td>BigQuery subscription（pattern 對照）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &#43; Dataflow &#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &lt; 100ms、BigQuery 並行存 raw recovery。">3.C68</a></td>
          <td>Wix clickstream + Dataflow + BQ</td>
          <td>BigQuery subscription / Push vs Pull</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/" data-link-title="3.C69 Twitter Ad Engagement：把 stream 切成多 topic 做 partition" data-link-desc="Twitter 把 80K msg/s stream 切成 6 個 topic 做 partition、Avro schema、Beam/Dataflow → Bigtable/BQ。">3.C69</a></td>
          <td>Twitter Ad Engagement topic 切分</td>
          <td>Schema enforcement / Ordering key</td>
      </tr>
  </tbody>
</table>
<h2 id="案例覆蓋缺口待補">案例覆蓋缺口（待補）</h2>
<p>下列大綱章節在本案例庫中<strong>公開 customer-side case 偏弱或缺</strong>、撰寫正文時要明示「以下分析依官方文件 / KIP / 通用模式推導、非 case-driven」：</p>
<ul>
<li><strong>Kafka KRaft</strong>：缺 customer-side 一手案例、目前依官方 KIP-833 / Confluent 公告為準</li>
<li><strong>RabbitMQ MQTT plugin + 多協議</strong>：缺 IoT 廠商 customer case、可補 RabbitMQ 官方 native MQTT blog</li>
<li><strong>RabbitMQ Cluster Operator（K8s）</strong>：缺直接案例、Zalando 案例是 pre-K8s 對照</li>
<li><strong>Redis Streams + Functions（Redis 7+）</strong>：公開 customer case 幾乎沒有</li>
<li><strong>Redis Cluster on Streams</strong>：公開 case 多在 single-instance / Sentinel 規模、Cluster 案例少</li>
<li><strong>Pub/Sub IAM + Service Account</strong>：customer engineering blog 著墨少、建議依 GCP 官方 IAM 文件 + 通用安全原則</li>
</ul>
<p>案例庫總計 69 個、其中 Kafka 17 個（含通用層 5）、RabbitMQ 11 個、NATS 8 個、Redis Streams 6 個、SQS 12 個（含通用層 1）、Pub/Sub 10 個、純通用 / 反例 4 個。</p>
]]></content:encoded></item><item><title>模組五案例正文</title><link>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/</guid><description>&lt;p>這個資料夾的核心責任是把平台遷移案例轉成部署策略、切流策略與回退策略的可操作內容。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/" data-link-title="5.C1 Tradeshift：self-managed Kubernetes 遷移到 EKS" data-link-desc="零停機平台遷移的分段策略案例。">5.C1&lt;/a>&lt;/td>
 &lt;td>Tradeshift：self-managed K8s -&amp;gt; EKS&lt;/td>
 &lt;td>把零停機平台遷移拆成可執行階段&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/" data-link-title="5.C2 Condé Nast：EKS 平台整併與標準化" data-link-desc="多地區異質 Kubernetes 平台整併為統一控制面的案例。">5.C2&lt;/a>&lt;/td>
 &lt;td>Condé Nast：EKS 平台整併&lt;/td>
 &lt;td>把多團隊異質集群整併成單一治理面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/orbitera-managed-kubernetes-migration/" data-link-title="5.C3 Orbitera：遷移到 Managed Kubernetes" data-link-desc="平台重置時如何讓產品不中斷地完成編排層轉換。">5.C3&lt;/a>&lt;/td>
 &lt;td>Orbitera：遷移到 managed Kubernetes&lt;/td>
 &lt;td>把平台重置與產品不中斷目標對齊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/mobileye-workloads-to-eks/" data-link-title="5.C4 Mobileye：Workloads 遷移到 EKS" data-link-desc="大規模工作負載遷移到 managed Kubernetes 的分段治理案例。">5.C4&lt;/a>&lt;/td>
 &lt;td>Mobileye：workloads -&amp;gt; EKS&lt;/td>
 &lt;td>把工作負載搬遷策略做成可回退階段&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/miro-managed-eks-migration/" data-link-title="5.C5 Miro：Managed EKS 遷移" data-link-desc="從自維運平台轉向 managed EKS 的組織與技術協同案例。">5.C5&lt;/a>&lt;/td>
 &lt;td>Miro：managed EKS 遷移&lt;/td>
 &lt;td>把平台託管化與團隊維運模型對齊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-kubernetes-cluster-scaling-evolution/" data-link-title="5.C6 Airbnb：Kubernetes 叢集擴縮演進" data-link-desc="從手動擴縮走向自動化容量治理的部署平台案例。">5.C6&lt;/a>&lt;/td>
 &lt;td>Airbnb K8s 叢集演進&lt;/td>
 &lt;td>把手動擴縮轉成自動化容量治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/airbnb-istio-upgrade-governance/" data-link-title="5.C7 Airbnb：Istio 升級治理" data-link-desc="service mesh 升級在大規模環境下如何保持高可用。">5.C7&lt;/a>&lt;/td>
 &lt;td>Airbnb Istio 升級治理&lt;/td>
 &lt;td>把 service mesh 升級變成可重播流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9&lt;/a>&lt;/td>
 &lt;td>反例：切流未先 drain&lt;/td>
 &lt;td>平台切換忽略連線清退造成錯誤暴增&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/cases/contrast-platform-migration-by-scale/" data-link-title="5.C10 對照：規模差異下的平台遷移" data-link-desc="平台遷移策略在小中大型組織下的差異。">5.C10&lt;/a>&lt;/td>
 &lt;td>對照：規模差異下平台遷移&lt;/td>
 &lt;td>小中大型組織的平台遷移風險邊界不同&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把平台遷移案例轉成部署策略、切流策略與回退策略的可操作內容。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/" data-link-title="5.C1 Tradeshift：self-managed Kubernetes 遷移到 EKS" data-link-desc="零停機平台遷移的分段策略案例。">5.C1</a></td>
          <td>Tradeshift：self-managed K8s -&gt; EKS</td>
          <td>把零停機平台遷移拆成可執行階段</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/conde-nast-platform-modernization-eks/" data-link-title="5.C2 Condé Nast：EKS 平台整併與標準化" data-link-desc="多地區異質 Kubernetes 平台整併為統一控制面的案例。">5.C2</a></td>
          <td>Condé Nast：EKS 平台整併</td>
          <td>把多團隊異質集群整併成單一治理面</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/orbitera-managed-kubernetes-migration/" data-link-title="5.C3 Orbitera：遷移到 Managed Kubernetes" data-link-desc="平台重置時如何讓產品不中斷地完成編排層轉換。">5.C3</a></td>
          <td>Orbitera：遷移到 managed Kubernetes</td>
          <td>把平台重置與產品不中斷目標對齊</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/mobileye-workloads-to-eks/" data-link-title="5.C4 Mobileye：Workloads 遷移到 EKS" data-link-desc="大規模工作負載遷移到 managed Kubernetes 的分段治理案例。">5.C4</a></td>
          <td>Mobileye：workloads -&gt; EKS</td>
          <td>把工作負載搬遷策略做成可回退階段</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/miro-managed-eks-migration/" data-link-title="5.C5 Miro：Managed EKS 遷移" data-link-desc="從自維運平台轉向 managed EKS 的組織與技術協同案例。">5.C5</a></td>
          <td>Miro：managed EKS 遷移</td>
          <td>把平台託管化與團隊維運模型對齊</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/airbnb-kubernetes-cluster-scaling-evolution/" data-link-title="5.C6 Airbnb：Kubernetes 叢集擴縮演進" data-link-desc="從手動擴縮走向自動化容量治理的部署平台案例。">5.C6</a></td>
          <td>Airbnb K8s 叢集演進</td>
          <td>把手動擴縮轉成自動化容量治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/airbnb-istio-upgrade-governance/" data-link-title="5.C7 Airbnb：Istio 升級治理" data-link-desc="service mesh 升級在大規模環境下如何保持高可用。">5.C7</a></td>
          <td>Airbnb Istio 升級治理</td>
          <td>把 service mesh 升級變成可重播流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/failure-platform-cutover-without-drain/" data-link-title="5.C9 反例：平台切流未先 Draining" data-link-desc="切流時忽略連線清退造成請求錯誤與重試風暴。">5.C9</a></td>
          <td>反例：切流未先 drain</td>
          <td>平台切換忽略連線清退造成錯誤暴增</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/cases/contrast-platform-migration-by-scale/" data-link-title="5.C10 對照：規模差異下的平台遷移" data-link-desc="平台遷移策略在小中大型組織下的差異。">5.C10</a></td>
          <td>對照：規模差異下平台遷移</td>
          <td>小中大型組織的平台遷移風險邊界不同</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>模組零案例正文</title><link>https://tarrragon.github.io/blog/backend/00-service-selection/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/00-service-selection/cases/</guid><description>&lt;p>這個資料夾的核心責任是把案例圖譜中的產業壓力轉成可回寫正文。圖譜負責索引，正文負責判讀訊號、風險邊界與下一步路由。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cases/fintech-compliance-and-selection-pressure/" data-link-title="FinTech：合規壓力下的後端選型" data-link-desc="在審計、留存與交易正確性要求下，如何平衡成本、風險與交付速度。">0.C1&lt;/a>&lt;/td>
 &lt;td>FinTech 合規壓力&lt;/td>
 &lt;td>把合規、留存、審計對選型的影響變成可判讀條件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cases/gaming-peak-traffic-and-isolation/" data-link-title="Gaming：高峰流量與隔離邊界選型" data-link-desc="大型活動流量下，如何在低延遲與穩定性之間做可持續取捨。">0.C2&lt;/a>&lt;/td>
 &lt;td>Gaming 高峰流量&lt;/td>
 &lt;td>把低延遲與高峰容量風險轉成分層決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cases/healthcare-data-sovereignty-and-recovery/" data-link-title="Healthcare：資料主權與回復順序選型" data-link-desc="醫療場景下，如何把資料主權、存取邊界與災難回復放進同一套決策。">0.C3&lt;/a>&lt;/td>
 &lt;td>Healthcare 資料主權&lt;/td>
 &lt;td>把資料主權與回復順序放進同一個選型模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/" data-link-title="營運後技術轉換：語言、工具與架構何時該換" data-link-desc="服務營運一段時間後，如何判讀何時該轉語言、工具或架構，並用案例說明轉換動機。">0.C4&lt;/a>&lt;/td>
 &lt;td>營運後技術轉換&lt;/td>
 &lt;td>營運成熟後何時要轉語言、工具或架構，以及轉換成本邏輯&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把案例圖譜中的產業壓力轉成可回寫正文。圖譜負責索引，正文負責判讀訊號、風險邊界與下一步路由。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/00-service-selection/cases/fintech-compliance-and-selection-pressure/" data-link-title="FinTech：合規壓力下的後端選型" data-link-desc="在審計、留存與交易正確性要求下，如何平衡成本、風險與交付速度。">0.C1</a></td>
          <td>FinTech 合規壓力</td>
          <td>把合規、留存、審計對選型的影響變成可判讀條件</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/00-service-selection/cases/gaming-peak-traffic-and-isolation/" data-link-title="Gaming：高峰流量與隔離邊界選型" data-link-desc="大型活動流量下，如何在低延遲與穩定性之間做可持續取捨。">0.C2</a></td>
          <td>Gaming 高峰流量</td>
          <td>把低延遲與高峰容量風險轉成分層決策</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/00-service-selection/cases/healthcare-data-sovereignty-and-recovery/" data-link-title="Healthcare：資料主權與回復順序選型" data-link-desc="醫療場景下，如何把資料主權、存取邊界與災難回復放進同一套決策。">0.C3</a></td>
          <td>Healthcare 資料主權</td>
          <td>把資料主權與回復順序放進同一個選型模型</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/" data-link-title="營運後技術轉換：語言、工具與架構何時該換" data-link-desc="服務營運一段時間後，如何判讀何時該轉語言、工具或架構，並用案例說明轉換動機。">0.C4</a></td>
          <td>營運後技術轉換</td>
          <td>營運成熟後何時要轉語言、工具或架構，以及轉換成本邏輯</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>模組七案例正文</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/</guid><description>&lt;p>這個資料夾的核心責任是把資安與控制平面事故轉成可回寫治理控制的案例正文。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-route-leak-2026/" data-link-title="7.C1 Cloudflare：2026 Route Leak 事件" data-link-desc="BGP 路由政策自動化失誤如何回寫控制面治理。">7.C1&lt;/a>&lt;/td>
 &lt;td>Cloudflare 2026 Route Leak&lt;/td>
 &lt;td>路由策略自動化失誤如何回寫治理與 tripwire&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/cloudflare-control-plane-token-2023/" data-link-title="7.C2 Cloudflare：2023 Control-plane Token 事件" data-link-desc="控制面 token 事件如何回寫 secrets 與機器憑證治理。">7.C2&lt;/a>&lt;/td>
 &lt;td>Cloudflare 2023 Token 事件&lt;/td>
 &lt;td>控制面 token 風險如何轉成機器憑證治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/azure-ad-identity-control-plane-2021/" data-link-title="7.C3 Azure AD：2021 Identity Control-plane 事件" data-link-desc="身分控制面事件如何影響多服務信任鏈與回復優先序。">7.C3&lt;/a>&lt;/td>
 &lt;td>Azure AD 2021 控制面事件&lt;/td>
 &lt;td>身分控制面事故如何影響多服務信任鏈&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/microsoft-storm-0558-signing-key-2023/" data-link-title="7.C4 Microsoft：Storm-0558 簽章金鑰事件" data-link-desc="簽章金鑰事件如何回寫 identity 信任邊界與觀測證據鏈。">7.C4&lt;/a>&lt;/td>
 &lt;td>Microsoft Storm-0558&lt;/td>
 &lt;td>簽章金鑰事件如何回寫 identity 信任邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-support-system-incident-2023/" data-link-title="7.C5 Okta：2023 Support System 事件" data-link-desc="支援系統憑證風險如何擴散到客戶租戶的案例。">7.C5&lt;/a>&lt;/td>
 &lt;td>Okta support 系統事件&lt;/td>
 &lt;td>支援系統憑證風險如何擴散到客戶租戶&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-cross-tenant-impersonation-2023/" data-link-title="7.C6 Okta：Cross-tenant Impersonation 防禦回寫" data-link-desc="跨租戶 impersonation 風險如何轉成身份治理與偵測策略。">7.C6&lt;/a>&lt;/td>
 &lt;td>Okta cross-tenant 事件&lt;/td>
 &lt;td>跨租戶 impersonation 如何回寫防禦與偵測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/okta-byo-telephony-security-shift/" data-link-title="7.C7 Okta：BYO Telephony 的身份安全責任轉換" data-link-desc="MFA 簡訊/語音路徑從平台托管轉向客戶自管的治理案例。">7.C7&lt;/a>&lt;/td>
 &lt;td>Okta BYO Telephony&lt;/td>
 &lt;td>MFA 供應鏈責任如何轉為客戶可控治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/failure-credential-rotation-without-scope/" data-link-title="7.C9 反例：憑證輪替未分 Scope" data-link-desc="憑證輪替若未分域分批，容易造成跨系統連鎖中斷。">7.C9&lt;/a>&lt;/td>
 &lt;td>反例：憑證輪替失敗&lt;/td>
 &lt;td>憑證輪替未分 scope 導致跨系統連鎖中斷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/contrast-identity-governance-by-scale/" data-link-title="7.C10 對照：規模差異下的身份治理" data-link-desc="identity 控制面治理在不同規模服務下的失敗邊界差異。">7.C10&lt;/a>&lt;/td>
 &lt;td>對照：規模差異下身份治理&lt;/td>
 &lt;td>不同規模服務在 identity 控制面的風險差異&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/" data-link-title="7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel" data-link-desc="以「手機遠端操作本機 shell」為情境，比較 Tailscale mesh VPN 與 Cloudflare Tunnel &amp;#43; Access 兩種存取模型的選型判讀。">7.C11&lt;/a>&lt;/td>
 &lt;td>選型：單人遠端 Shell&lt;/td>
 &lt;td>單人遠端 Shell 情境下的 tunnel 選型判讀&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把資安與控制平面事故轉成可回寫治理控制的案例正文。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/cloudflare-route-leak-2026/" data-link-title="7.C1 Cloudflare：2026 Route Leak 事件" data-link-desc="BGP 路由政策自動化失誤如何回寫控制面治理。">7.C1</a></td>
          <td>Cloudflare 2026 Route Leak</td>
          <td>路由策略自動化失誤如何回寫治理與 tripwire</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/cloudflare-control-plane-token-2023/" data-link-title="7.C2 Cloudflare：2023 Control-plane Token 事件" data-link-desc="控制面 token 事件如何回寫 secrets 與機器憑證治理。">7.C2</a></td>
          <td>Cloudflare 2023 Token 事件</td>
          <td>控制面 token 風險如何轉成機器憑證治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/azure-ad-identity-control-plane-2021/" data-link-title="7.C3 Azure AD：2021 Identity Control-plane 事件" data-link-desc="身分控制面事件如何影響多服務信任鏈與回復優先序。">7.C3</a></td>
          <td>Azure AD 2021 控制面事件</td>
          <td>身分控制面事故如何影響多服務信任鏈</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/microsoft-storm-0558-signing-key-2023/" data-link-title="7.C4 Microsoft：Storm-0558 簽章金鑰事件" data-link-desc="簽章金鑰事件如何回寫 identity 信任邊界與觀測證據鏈。">7.C4</a></td>
          <td>Microsoft Storm-0558</td>
          <td>簽章金鑰事件如何回寫 identity 信任邊界</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/okta-support-system-incident-2023/" data-link-title="7.C5 Okta：2023 Support System 事件" data-link-desc="支援系統憑證風險如何擴散到客戶租戶的案例。">7.C5</a></td>
          <td>Okta support 系統事件</td>
          <td>支援系統憑證風險如何擴散到客戶租戶</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/okta-cross-tenant-impersonation-2023/" data-link-title="7.C6 Okta：Cross-tenant Impersonation 防禦回寫" data-link-desc="跨租戶 impersonation 風險如何轉成身份治理與偵測策略。">7.C6</a></td>
          <td>Okta cross-tenant 事件</td>
          <td>跨租戶 impersonation 如何回寫防禦與偵測</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/okta-byo-telephony-security-shift/" data-link-title="7.C7 Okta：BYO Telephony 的身份安全責任轉換" data-link-desc="MFA 簡訊/語音路徑從平台托管轉向客戶自管的治理案例。">7.C7</a></td>
          <td>Okta BYO Telephony</td>
          <td>MFA 供應鏈責任如何轉為客戶可控治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/failure-credential-rotation-without-scope/" data-link-title="7.C9 反例：憑證輪替未分 Scope" data-link-desc="憑證輪替若未分域分批，容易造成跨系統連鎖中斷。">7.C9</a></td>
          <td>反例：憑證輪替失敗</td>
          <td>憑證輪替未分 scope 導致跨系統連鎖中斷</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/contrast-identity-governance-by-scale/" data-link-title="7.C10 對照：規模差異下的身份治理" data-link-desc="identity 控制面治理在不同規模服務下的失敗邊界差異。">7.C10</a></td>
          <td>對照：規模差異下身份治理</td>
          <td>不同規模服務在 identity 控制面的風險差異</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/" data-link-title="7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel" data-link-desc="以「手機遠端操作本機 shell」為情境，比較 Tailscale mesh VPN 與 Cloudflare Tunnel &#43; Access 兩種存取模型的選型判讀。">7.C11</a></td>
          <td>選型：單人遠端 Shell</td>
          <td>單人遠端 Shell 情境下的 tunnel 選型判讀</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>UX 設計案例庫</title><link>https://tarrragon.github.io/blog/ux-design/cases/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/</guid><description>&lt;p>這個資料夾收錄 UX 設計的實戰案例 — 重點不在「畫面怎麼設計」，而在「設計方法的哪個步驟遺漏了什麼」。每個案例記錄一個真實的 UX 缺口、分析企劃階段的遺漏機制、提出系統性的預防方法。&lt;/p>
&lt;p>案例來源分兩類：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>自有案例&lt;/strong>：app_tunnel 專案的實機測試教訓（first-party，有完整程式碼和 commit 歷史）&lt;/li>
&lt;li>&lt;strong>外部案例&lt;/strong>：iOS/Android 設計指南中的反模式和社群討論（third-party，引用公開來源）&lt;/li>
&lt;/ul>
&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>mobile CLI app 的鍵盤設計案例&lt;/td>
 &lt;td>小眾需求，公開案例稀少&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模組五（導航模式）&lt;/td>
 &lt;td>GoRouter vs Navigator 2.0 的導航 UX 差異案例&lt;/td>
 &lt;td>Flutter 社群有討論但少系統化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>來源&lt;/th>
 &lt;th>模組&lt;/th>
 &lt;th>缺口類型&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1&lt;/a>&lt;/td>
 &lt;td>五個狀態零個退出路徑&lt;/td>
 &lt;td>app_tunnel&lt;/td>
 &lt;td>模組一&lt;/td>
 &lt;td>狀態機退出路徑未設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2&lt;/a>&lt;/td>
 &lt;td>biometricOnly=true 無密碼 fallback&lt;/td>
 &lt;td>app_tunnel&lt;/td>
 &lt;td>模組二&lt;/td>
 &lt;td>Gate 無 fallback&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>| &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &amp;#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3&lt;/a> | 終端機文字輸入機制未設計 | app_tunnel | 模組三 | 輸入機制是事後 hotfix |
| &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/missing-enrollment-entry-point/" data-link-title="U.C4 首頁缺配對入口按鈕、導航流未完整列出" data-link-desc="Flutter app 首頁只有 Connect Terminal 按鈕、沒有 Enroll Device 入口 — 使用者首次使用時找不到配對功能。根因是導航流設計只考慮了日常操作（UC-02 連線）、遺漏了首次操作（UC-01 配對）的入口">U.C4&lt;/a> | 首頁缺配對入口按鈕 | app_tunnel | 模組一 | 導航流未完整列出 |&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾收錄 UX 設計的實戰案例 — 重點不在「畫面怎麼設計」，而在「設計方法的哪個步驟遺漏了什麼」。每個案例記錄一個真實的 UX 缺口、分析企劃階段的遺漏機制、提出系統性的預防方法。</p>
<p>案例來源分兩類：</p>
<ul>
<li><strong>自有案例</strong>：app_tunnel 專案的實機測試教訓（first-party，有完整程式碼和 commit 歷史）</li>
<li><strong>外部案例</strong>：iOS/Android 設計指南中的反模式和社群討論（third-party，引用公開來源）</li>
</ul>
<h2 id="案例覆蓋缺口">案例覆蓋缺口</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>缺口</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模組三（輸入機制設計）</td>
          <td>mobile CLI app 的鍵盤設計案例</td>
          <td>小眾需求，公開案例稀少</td>
      </tr>
      <tr>
          <td>模組五（導航模式）</td>
          <td>GoRouter vs Navigator 2.0 的導航 UX 差異案例</td>
          <td>Flutter 社群有討論但少系統化</td>
      </tr>
  </tbody>
</table>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>主題</th>
          <th>來源</th>
          <th>模組</th>
          <th>缺口類型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1</a></td>
          <td>五個狀態零個退出路徑</td>
          <td>app_tunnel</td>
          <td>模組一</td>
          <td>狀態機退出路徑未設計</td>
      </tr>
      <tr>
          <td><a href="/blog/ux-design/cases/biometric-only-no-fallback/" data-link-title="U.C2 biometricOnly=true 無密碼 fallback" data-link-desc="Flutter app 的生物辨識設定 biometricOnly: true 阻擋所有非生物辨識認證方式 — Face ID 不可用時使用者直接被擋住，沒有替代路徑">U.C2</a></td>
          <td>biometricOnly=true 無密碼 fallback</td>
          <td>app_tunnel</td>
          <td>模組二</td>
          <td>Gate 無 fallback</td>
      </tr>
  </tbody>
</table>
<p>| <a href="/blog/ux-design/cases/terminal-input-mechanism-absent/" data-link-title="U.C3 終端機文字輸入機制未設計、事後 hotfix 補 TextField" data-link-desc="Flutter 終端機 app 的鍵盤輸入完全未設計 — 沒有 TextField、沒有 keyboard type 選擇、沒有 IME 控制。W2 修復時才補上 TextField &#43; 6 個參數（enableSuggestions/autocorrect/enableIMEPersonalizedLearning/keyboardType/textInputAction/onSubmitted），全是散落 hotfix">U.C3</a> | 終端機文字輸入機制未設計 | app_tunnel | 模組三 | 輸入機制是事後 hotfix |
| <a href="/blog/ux-design/cases/missing-enrollment-entry-point/" data-link-title="U.C4 首頁缺配對入口按鈕、導航流未完整列出" data-link-desc="Flutter app 首頁只有 Connect Terminal 按鈕、沒有 Enroll Device 入口 — 使用者首次使用時找不到配對功能。根因是導航流設計只考慮了日常操作（UC-02 連線）、遺漏了首次操作（UC-01 配對）的入口">U.C4</a>  | 首頁缺配對入口按鈕       | app_tunnel | 模組一 | 導航流未完整列出      |</p>
]]></content:encoded></item><item><title>開發測試案例庫</title><link>https://tarrragon.github.io/blog/testing/cases/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/testing/cases/</guid><description>&lt;p>這個資料夾收錄測試策略的實戰案例 — 重點不在「測試怎麼寫」，而在「測試為什麼沒抓到問題」。每個案例記錄一個真實的測試盲區、分析遮蔽機制、提出可重用的防護策略。&lt;/p>
&lt;p>案例來源分兩類：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>自有案例&lt;/strong>：app_tunnel 專案的實機測試教訓（first-party，有完整程式碼和 commit 歷史）&lt;/li>
&lt;li>&lt;strong>外部案例&lt;/strong>：開源專案和社群的已知測試陷阱（third-party，引用公開來源）&lt;/li>
&lt;/ul>
&lt;h2 id="案例覆蓋缺口">案例覆蓋缺口&lt;/h2>
&lt;p>以下章節目前公開 case 稀薄，Stage 0 採集後視覆蓋情況補強：&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>模組二（客戶端可觀測性）&lt;/td>
 &lt;td>自架 log endpoint 的實戰案例&lt;/td>
 &lt;td>多數公開案例偏商業方案（Sentry/Datadog）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模組四（自動化 UI 驗證）&lt;/td>
 &lt;td>widget test 狀態覆蓋的 false negative 案例&lt;/td>
 &lt;td>待採集 Flutter/React 社群案例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模組五（測試設計判斷）&lt;/td>
 &lt;td>flaky test 根因分類的量化案例&lt;/td>
 &lt;td>CI 平台有公開統計但散落&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>來源&lt;/th>
 &lt;th>測試層&lt;/th>
 &lt;th>遮蔽機制&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1&lt;/a>&lt;/td>
 &lt;td>WebSocket text/binary frame 被 mock 遮蔽&lt;/td>
 &lt;td>app_tunnel&lt;/td>
 &lt;td>protocol-integration&lt;/td>
 &lt;td>mock 不區分 frame type&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>Auth handshake 邏輯缺失被 mock 遮蔽&lt;/td>
 &lt;td>app_tunnel&lt;/td>
 &lt;td>protocol-integration&lt;/td>
 &lt;td>mock 不需認證&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>| &lt;a href="https://tarrragon.github.io/blog/testing/cases/ansi-parser-test-data-blindspot/" data-link-title="T.C3 ANSI parser 測試資料不覆蓋真實 shell output" data-link-desc="ANSI parser 只處理基本 SGR 色彩碼、unit test 用手寫乾淨字串驗證 — 真實 zsh prompt 送出 OSC 標題設定、CSI private mode 游標隱藏、括號貼上模式等數十種控制序列，全部殘留為亂碼">T.C3&lt;/a> | ANSI parser 測試資料不覆蓋真實 shell output | app_tunnel | unit-test-data | 手寫測試字串是乾淨子集 |
| &lt;a href="https://tarrragon.github.io/blog/testing/cases/client-log-absent-debug-cost/" data-link-title="T.C4 Client-side log 缺失導致 debug 只能靠實機盲測" data-link-desc="Flutter app 六個核心元件中只有兩個有 log（且全是 W2 hotfix 補的），連線失敗時開發者無法從任何 log 判斷失敗發生在哪一步 — 被迫用最昂貴的 debug 方式：插拔裝置反覆測試">T.C4&lt;/a> | Client-side log 缺失導致 debug 只能靠實機 | app_tunnel | observability | 企劃階段未設計 log 點 |&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾收錄測試策略的實戰案例 — 重點不在「測試怎麼寫」，而在「測試為什麼沒抓到問題」。每個案例記錄一個真實的測試盲區、分析遮蔽機制、提出可重用的防護策略。</p>
<p>案例來源分兩類：</p>
<ul>
<li><strong>自有案例</strong>：app_tunnel 專案的實機測試教訓（first-party，有完整程式碼和 commit 歷史）</li>
<li><strong>外部案例</strong>：開源專案和社群的已知測試陷阱（third-party，引用公開來源）</li>
</ul>
<h2 id="案例覆蓋缺口">案例覆蓋缺口</h2>
<p>以下章節目前公開 case 稀薄，Stage 0 採集後視覆蓋情況補強：</p>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>缺口</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>模組二（客戶端可觀測性）</td>
          <td>自架 log endpoint 的實戰案例</td>
          <td>多數公開案例偏商業方案（Sentry/Datadog）</td>
      </tr>
      <tr>
          <td>模組四（自動化 UI 驗證）</td>
          <td>widget test 狀態覆蓋的 false negative 案例</td>
          <td>待採集 Flutter/React 社群案例</td>
      </tr>
      <tr>
          <td>模組五（測試設計判斷）</td>
          <td>flaky test 根因分類的量化案例</td>
          <td>CI 平台有公開統計但散落</td>
      </tr>
  </tbody>
</table>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>主題</th>
          <th>來源</th>
          <th>測試層</th>
          <th>遮蔽機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/testing/cases/ws-text-binary-frame-mock-blindspot/" data-link-title="T.C1 WebSocket text/binary frame 被 FakeWebSocketChannel 遮蔽" data-link-desc="Flutter app 用 Uint8List 發送 WS 資料走 binary frame，ttyd 期望 text frame 靜默忽略 — FakeWebSocketChannel 的 sink.add 接受 dynamic 不區分 frame type，192 個 test 全過但實機無回應">T.C1</a></td>
          <td>WebSocket text/binary frame 被 mock 遮蔽</td>
          <td>app_tunnel</td>
          <td>protocol-integration</td>
          <td>mock 不區分 frame type</td>
      </tr>
      <tr>
          <td><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></td>
          <td>Auth handshake 邏輯缺失被 mock 遮蔽</td>
          <td>app_tunnel</td>
          <td>protocol-integration</td>
          <td>mock 不需認證</td>
      </tr>
  </tbody>
</table>
<p>| <a href="/blog/testing/cases/ansi-parser-test-data-blindspot/" data-link-title="T.C3 ANSI parser 測試資料不覆蓋真實 shell output" data-link-desc="ANSI parser 只處理基本 SGR 色彩碼、unit test 用手寫乾淨字串驗證 — 真實 zsh prompt 送出 OSC 標題設定、CSI private mode 游標隱藏、括號貼上模式等數十種控制序列，全部殘留為亂碼">T.C3</a> | ANSI parser 測試資料不覆蓋真實 shell output | app_tunnel | unit-test-data | 手寫測試字串是乾淨子集 |
| <a href="/blog/testing/cases/client-log-absent-debug-cost/" data-link-title="T.C4 Client-side log 缺失導致 debug 只能靠實機盲測" data-link-desc="Flutter app 六個核心元件中只有兩個有 log（且全是 W2 hotfix 補的），連線失敗時開發者無法從任何 log 判斷失敗發生在哪一步 — 被迫用最昂貴的 debug 方式：插拔裝置反覆測試">T.C4</a>    | Client-side log 缺失導致 debug 只能靠實機   | app_tunnel | observability  | 企劃階段未設計 log 點  |</p>
]]></content:encoded></item><item><title>監控案例庫</title><link>https://tarrragon.github.io/blog/monitoring/cases/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/cases/</guid><description>&lt;p>本案例庫的來源與 testing / ux-design 不同：案例由 &lt;a href="https://github.com/tarrragon/monitor">tarrragon/monitor&lt;/a> 的實作過程產生，不是事前採集。&lt;/p>
&lt;p>每個案例對應 monitor repo 的 &lt;code>docs/challenges/&lt;/code> 中的一個撞牆記錄，經教學化處理後收錄於此。&lt;/p>
&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>JSONL 查詢效能天花板&lt;/td>
 &lt;td>累積 &amp;gt; 1 萬筆&lt;/td>
 &lt;td>模組四&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高併發寫入 buffer 策略&lt;/td>
 &lt;td>多 SDK 同時 flush&lt;/td>
 &lt;td>模組四&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SDK 離線 buffer 丟失&lt;/td>
 &lt;td>網路中斷 + buffer 滿&lt;/td>
 &lt;td>模組三&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨平台 timestamp 偏移&lt;/td>
 &lt;td>JS/Dart/Python 時間精度不同&lt;/td>
 &lt;td>模組五&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>錯誤去重 fingerprint 設計&lt;/td>
 &lt;td>同一 exception 重複回報&lt;/td>
 &lt;td>模組三&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Redaction false positive&lt;/td>
 &lt;td>正常內容被誤判為 secret&lt;/td>
 &lt;td>模組七&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>聚合查詢掃描量爆炸&lt;/td>
 &lt;td>「過去 7 天趨勢」&lt;/td>
 &lt;td>模組四&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>案例庫會隨實作進展持續擴充。&lt;/p></description><content:encoded><![CDATA[<p>本案例庫的來源與 testing / ux-design 不同：案例由 <a href="https://github.com/tarrragon/monitor">tarrragon/monitor</a> 的實作過程產生，不是事前採集。</p>
<p>每個案例對應 monitor repo 的 <code>docs/challenges/</code> 中的一個撞牆記錄，經教學化處理後收錄於此。</p>
<h2 id="預期案例實作後產生">預期案例（實作後產生）</h2>
<table>
  <thead>
      <tr>
          <th>預期主題</th>
          <th>觸發時機</th>
          <th>對應模組</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSONL 查詢效能天花板</td>
          <td>累積 &gt; 1 萬筆</td>
          <td>模組四</td>
      </tr>
      <tr>
          <td>高併發寫入 buffer 策略</td>
          <td>多 SDK 同時 flush</td>
          <td>模組四</td>
      </tr>
      <tr>
          <td>SDK 離線 buffer 丟失</td>
          <td>網路中斷 + buffer 滿</td>
          <td>模組三</td>
      </tr>
      <tr>
          <td>跨平台 timestamp 偏移</td>
          <td>JS/Dart/Python 時間精度不同</td>
          <td>模組五</td>
      </tr>
      <tr>
          <td>錯誤去重 fingerprint 設計</td>
          <td>同一 exception 重複回報</td>
          <td>模組三</td>
      </tr>
      <tr>
          <td>Redaction false positive</td>
          <td>正常內容被誤判為 secret</td>
          <td>模組七</td>
      </tr>
      <tr>
          <td>聚合查詢掃描量爆炸</td>
          <td>「過去 7 天趨勢」</td>
          <td>模組四</td>
      </tr>
  </tbody>
</table>
<p>案例庫會隨實作進展持續擴充。</p>
]]></content:encoded></item><item><title>可靠性服務案例庫</title><link>https://tarrragon.github.io/blog/backend/06-reliability/cases/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/cases/</guid><description>&lt;p>本案例庫以服務為單位、收錄公開 SRE 實踐（SRE Book / 工程部落格 / 演講 / paper）。每個服務一個資料夾，累積該服務的可靠性工程文化、failure mode 與 chaos / DR 案例。&lt;/p>
&lt;p>服務分層依 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">模組六 _index&lt;/a> 的 T1 / T2 / T3 規劃。重複出現於 06 / 08 的服務（stripe / cloudflare / linkedin）資料夾住在主要教學模組、跨模組以連結互通。&lt;/p>
&lt;h2 id="t1-服務">T1 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/" data-link-title="Google" data-link-desc="Google SRE 實踐原典：SLI / SLO / Error Budget / Postmortem 文化">google&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/" data-link-title="Netflix" data-link-desc="Netflix Chaos Engineering 起源：Simian Army / FIT / 規模化故障注入">netflix&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/" data-link-title="Amazon" data-link-desc="Amazon Cell-based Architecture / Shuffle Sharding / Blast Radius 設計">amazon&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">stripe&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/" data-link-title="Shopify" data-link-desc="Shopify BFCM Scaling / Pod-based Isolation / Capacity Planning">shopify&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="t1-第一批正文已完成">T1 第一批正文（已完成）&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>Google&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">G1 Error Budget 與 Release Gating&lt;/a>&lt;/td>
 &lt;td>可靠性消耗如何直接決定發布節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Netflix&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">N1 Steady State、Chaos 與 FIT&lt;/a>&lt;/td>
 &lt;td>故障注入如何變成可證偽流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1 Shuffle Sharding 與 Cell 邊界&lt;/a>&lt;/td>
 &lt;td>多租戶故障如何被局部化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Stripe&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Idempotency 與零停機遷移&lt;/a>&lt;/td>
 &lt;td>交易重試與遷移如何共用一致性模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1 BFCM 容量治理與 Game Day&lt;/a>&lt;/td>
 &lt;td>峰值風險如何在活動前被消化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t1-第二批正文已完成">T1 第二批正文（已完成）&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>Amazon&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">A2 Static Stability 與 Constant Work&lt;/a>&lt;/td>
 &lt;td>控制面失效時資料面如何維持服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Stripe&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">S2 Canary Deploy 與 Progressive Rollout&lt;/a>&lt;/td>
 &lt;td>金流場景的放行節奏與交易指標驅動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shopify&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">H2 Pod Architecture 與 Resiliency Matrix&lt;/a>&lt;/td>
 &lt;td>多租戶隔離與系統化失敗模式盤點&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t1-深挖批次已完成">T1 深挖批次（已完成）&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>Google&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2 Postmortem Action Item Closure 治理&lt;/a>&lt;/td>
 &lt;td>事故教訓如何轉成有 owner 的改進項&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Google&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/" data-link-title="Google：Toil Budget 與 Automation 投資政策" data-link-desc="把 toil 從感受問題轉成預算問題：用時間配比與自動化回報機制，避免 on-call 壓力長期侵蝕可靠性工程。">G3 Toil Budget 與 Automation 投資政策&lt;/a>&lt;/td>
 &lt;td>值班壓力如何轉成工程投資決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Netflix&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">N2 Business-Hours Chaos Guardrails&lt;/a>&lt;/td>
 &lt;td>business hours 故障注入的安全邊界設計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Netflix&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">N3 FIT 證據交接與 Release Gate 回寫&lt;/a>&lt;/td>
 &lt;td>故障注入結果如何結構化驅動放行決策&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t2-服務">T2 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">linkedin&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/" data-link-title="Honeycomb" data-link-desc="Honeycomb Observability-driven SRE 與 SLO 實作">honeycomb&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">cloudflare（住於 08）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/" data-link-title="Microsoft / Azure SRE" data-link-desc="Microsoft Azure SRE Practices 與 Resilience Patterns">microsoft&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="t2t3-第一批正文已完成">T2/T3 第一批正文（已完成）&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>LinkedIn&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1 Capacity 與 On-call 分層&lt;/a>&lt;/td>
 &lt;td>容量邊界與值班交接協同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Honeycomb&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">HC1 Burn Rate 驅動可靠性&lt;/a>&lt;/td>
 &lt;td>用 SLO 消耗速度驅動行動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Microsoft&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">MS1 變更治理與可靠性門檻&lt;/a>&lt;/td>
 &lt;td>變更分層與 release gate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Spotify&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">SP1 平台工程與可靠性契約&lt;/a>&lt;/td>
 &lt;td>分散團隊共用可靠性基線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pinterest&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">P1 快取可靠性與容量驚奇&lt;/a>&lt;/td>
 &lt;td>命中率崩落時的恢復節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meta&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">M1 Region Failover 邊界治理&lt;/a>&lt;/td>
 &lt;td>跨區擴散與回復順序治理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t2t3-第二批正文已完成">T2/T3 第二批正文（已完成）&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>LinkedIn&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">L2 Automated Load Testing 與 Capacity Forecasting&lt;/a>&lt;/td>
 &lt;td>持續壓測驅動容量預測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Meta&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">M2 BGP 事故與控制面恢復順序&lt;/a>&lt;/td>
 &lt;td>回復工具依賴已故障系統的恢復困境&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Honeycomb&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/" data-link-title="Honeycomb：Production Excellence 與 Test in Production" data-link-desc="用 high-cardinality observability 把 production 變成安全的驗證環境：feature flag、progressive rollout 與即時回饋的配合。">HC2 Production Excellence 與 Test in Production&lt;/a>&lt;/td>
 &lt;td>observability-driven 生產驗證文化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Microsoft&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/" data-link-title="Microsoft：Safe Deployment Practices 與 Resilience Patterns" data-link-desc="大型 SaaS 用 ring-based deployment 控制變更擴散，用標準化 resilience patterns 讓依賴失效時的降級行為可預測。">MS2 Safe Deployment Practices 與 Resilience Patterns&lt;/a>&lt;/td>
 &lt;td>ring-based deployment 與韌性設計模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Spotify&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/" data-link-title="Spotify：Backstage Service Catalog 與 Reliability Metadata" data-link-desc="用 service catalog 治理分散團隊的可靠性資訊：ownership、SLO 狀態、依賴圖與 runbook 的單一入口。">SP2 Backstage Service Catalog 與 Reliability Metadata&lt;/a>&lt;/td>
 &lt;td>service catalog 治理可靠性資訊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pinterest&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/" data-link-title="Pinterest：Storage Migration 與 Data Infrastructure Reliability" data-link-desc="大規模儲存遷移的可靠性設計：用 dual-write、shadow read 與 staged cutover 讓 PB 級資料基礎設施變更可漸進、可驗證、可回退。">P2 Storage Migration 與 Data Infrastructure Reliability&lt;/a>&lt;/td>
 &lt;td>大規模儲存遷移的驗證流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t3-服務">T3 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/spotify/" data-link-title="Spotify" data-link-desc="Spotify Chaos Engineering 與 Squad-based SRE">spotify&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/" data-link-title="Pinterest" data-link-desc="Pinterest Capacity Planning 與儲存架構可靠性">pinterest&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/" data-link-title="Meta / Facebook" data-link-desc="Meta Reliability Engineering 與超大規模事故學習">meta&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本案例庫以服務為單位、收錄公開 SRE 實踐（SRE Book / 工程部落格 / 演講 / paper）。每個服務一個資料夾，累積該服務的可靠性工程文化、failure mode 與 chaos / DR 案例。</p>
<p>服務分層依 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">模組六 _index</a> 的 T1 / T2 / T3 規劃。重複出現於 06 / 08 的服務（stripe / cloudflare / linkedin）資料夾住在主要教學模組、跨模組以連結互通。</p>
<h2 id="t1-服務">T1 服務</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/google/" data-link-title="Google" data-link-desc="Google SRE 實踐原典：SLI / SLO / Error Budget / Postmortem 文化">google</a></li>
<li><a href="/blog/backend/06-reliability/cases/netflix/" data-link-title="Netflix" data-link-desc="Netflix Chaos Engineering 起源：Simian Army / FIT / 規模化故障注入">netflix</a></li>
<li><a href="/blog/backend/06-reliability/cases/amazon/" data-link-title="Amazon" data-link-desc="Amazon Cell-based Architecture / Shuffle Sharding / Blast Radius 設計">amazon</a></li>
<li><a href="/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">stripe</a></li>
<li><a href="/blog/backend/06-reliability/cases/shopify/" data-link-title="Shopify" data-link-desc="Shopify BFCM Scaling / Pod-based Isolation / Capacity Planning">shopify</a></li>
</ul>
<h2 id="t1-第一批正文已完成">T1 第一批正文（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Google</td>
          <td><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">G1 Error Budget 與 Release Gating</a></td>
          <td>可靠性消耗如何直接決定發布節奏</td>
      </tr>
      <tr>
          <td>Netflix</td>
          <td><a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">N1 Steady State、Chaos 與 FIT</a></td>
          <td>故障注入如何變成可證偽流程</td>
      </tr>
      <tr>
          <td>Amazon</td>
          <td><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1 Shuffle Sharding 與 Cell 邊界</a></td>
          <td>多租戶故障如何被局部化</td>
      </tr>
      <tr>
          <td>Stripe</td>
          <td><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Idempotency 與零停機遷移</a></td>
          <td>交易重試與遷移如何共用一致性模型</td>
      </tr>
      <tr>
          <td>Shopify</td>
          <td><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1 BFCM 容量治理與 Game Day</a></td>
          <td>峰值風險如何在活動前被消化</td>
      </tr>
  </tbody>
</table>
<h2 id="t1-第二批正文已完成">T1 第二批正文（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Amazon</td>
          <td><a href="/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">A2 Static Stability 與 Constant Work</a></td>
          <td>控制面失效時資料面如何維持服務</td>
      </tr>
      <tr>
          <td>Stripe</td>
          <td><a href="/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">S2 Canary Deploy 與 Progressive Rollout</a></td>
          <td>金流場景的放行節奏與交易指標驅動</td>
      </tr>
      <tr>
          <td>Shopify</td>
          <td><a href="/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">H2 Pod Architecture 與 Resiliency Matrix</a></td>
          <td>多租戶隔離與系統化失敗模式盤點</td>
      </tr>
  </tbody>
</table>
<h2 id="t1-深挖批次已完成">T1 深挖批次（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Google</td>
          <td><a href="/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2 Postmortem Action Item Closure 治理</a></td>
          <td>事故教訓如何轉成有 owner 的改進項</td>
      </tr>
      <tr>
          <td>Google</td>
          <td><a href="/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/" data-link-title="Google：Toil Budget 與 Automation 投資政策" data-link-desc="把 toil 從感受問題轉成預算問題：用時間配比與自動化回報機制，避免 on-call 壓力長期侵蝕可靠性工程。">G3 Toil Budget 與 Automation 投資政策</a></td>
          <td>值班壓力如何轉成工程投資決策</td>
      </tr>
      <tr>
          <td>Netflix</td>
          <td><a href="/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">N2 Business-Hours Chaos Guardrails</a></td>
          <td>business hours 故障注入的安全邊界設計</td>
      </tr>
      <tr>
          <td>Netflix</td>
          <td><a href="/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">N3 FIT 證據交接與 Release Gate 回寫</a></td>
          <td>故障注入結果如何結構化驅動放行決策</td>
      </tr>
  </tbody>
</table>
<h2 id="t2-服務">T2 服務</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">linkedin</a></li>
<li><a href="/blog/backend/06-reliability/cases/honeycomb/" data-link-title="Honeycomb" data-link-desc="Honeycomb Observability-driven SRE 與 SLO 實作">honeycomb</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">cloudflare（住於 08）</a></li>
<li><a href="/blog/backend/06-reliability/cases/microsoft/" data-link-title="Microsoft / Azure SRE" data-link-desc="Microsoft Azure SRE Practices 與 Resilience Patterns">microsoft</a></li>
</ul>
<h2 id="t2t3-第一批正文已完成">T2/T3 第一批正文（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LinkedIn</td>
          <td><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1 Capacity 與 On-call 分層</a></td>
          <td>容量邊界與值班交接協同</td>
      </tr>
      <tr>
          <td>Honeycomb</td>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">HC1 Burn Rate 驅動可靠性</a></td>
          <td>用 SLO 消耗速度驅動行動</td>
      </tr>
      <tr>
          <td>Microsoft</td>
          <td><a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">MS1 變更治理與可靠性門檻</a></td>
          <td>變更分層與 release gate</td>
      </tr>
      <tr>
          <td>Spotify</td>
          <td><a href="/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">SP1 平台工程與可靠性契約</a></td>
          <td>分散團隊共用可靠性基線</td>
      </tr>
      <tr>
          <td>Pinterest</td>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">P1 快取可靠性與容量驚奇</a></td>
          <td>命中率崩落時的恢復節奏</td>
      </tr>
      <tr>
          <td>Meta</td>
          <td><a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">M1 Region Failover 邊界治理</a></td>
          <td>跨區擴散與回復順序治理</td>
      </tr>
  </tbody>
</table>
<h2 id="t2t3-第二批正文已完成">T2/T3 第二批正文（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>LinkedIn</td>
          <td><a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">L2 Automated Load Testing 與 Capacity Forecasting</a></td>
          <td>持續壓測驅動容量預測</td>
      </tr>
      <tr>
          <td>Meta</td>
          <td><a href="/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">M2 BGP 事故與控制面恢復順序</a></td>
          <td>回復工具依賴已故障系統的恢復困境</td>
      </tr>
      <tr>
          <td>Honeycomb</td>
          <td><a href="/blog/backend/06-reliability/cases/honeycomb/production-excellence-and-test-in-production/" data-link-title="Honeycomb：Production Excellence 與 Test in Production" data-link-desc="用 high-cardinality observability 把 production 變成安全的驗證環境：feature flag、progressive rollout 與即時回饋的配合。">HC2 Production Excellence 與 Test in Production</a></td>
          <td>observability-driven 生產驗證文化</td>
      </tr>
      <tr>
          <td>Microsoft</td>
          <td><a href="/blog/backend/06-reliability/cases/microsoft/safe-deployment-practices-and-resilience-patterns/" data-link-title="Microsoft：Safe Deployment Practices 與 Resilience Patterns" data-link-desc="大型 SaaS 用 ring-based deployment 控制變更擴散，用標準化 resilience patterns 讓依賴失效時的降級行為可預測。">MS2 Safe Deployment Practices 與 Resilience Patterns</a></td>
          <td>ring-based deployment 與韌性設計模式</td>
      </tr>
      <tr>
          <td>Spotify</td>
          <td><a href="/blog/backend/06-reliability/cases/spotify/backstage-service-catalog-and-reliability-metadata/" data-link-title="Spotify：Backstage Service Catalog 與 Reliability Metadata" data-link-desc="用 service catalog 治理分散團隊的可靠性資訊：ownership、SLO 狀態、依賴圖與 runbook 的單一入口。">SP2 Backstage Service Catalog 與 Reliability Metadata</a></td>
          <td>service catalog 治理可靠性資訊</td>
      </tr>
      <tr>
          <td>Pinterest</td>
          <td><a href="/blog/backend/06-reliability/cases/pinterest/storage-migration-and-data-infrastructure-reliability/" data-link-title="Pinterest：Storage Migration 與 Data Infrastructure Reliability" data-link-desc="大規模儲存遷移的可靠性設計：用 dual-write、shadow read 與 staged cutover 讓 PB 級資料基礎設施變更可漸進、可驗證、可回退。">P2 Storage Migration 與 Data Infrastructure Reliability</a></td>
          <td>大規模儲存遷移的驗證流程</td>
      </tr>
  </tbody>
</table>
<h2 id="t3-服務">T3 服務</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/spotify/" data-link-title="Spotify" data-link-desc="Spotify Chaos Engineering 與 Squad-based SRE">spotify</a></li>
<li><a href="/blog/backend/06-reliability/cases/pinterest/" data-link-title="Pinterest" data-link-desc="Pinterest Capacity Planning 與儲存架構可靠性">pinterest</a></li>
<li><a href="/blog/backend/06-reliability/cases/meta/" data-link-title="Meta / Facebook" data-link-desc="Meta Reliability Engineering 與超大規模事故學習">meta</a></li>
</ul>
]]></content:encoded></item><item><title>事故處理服務案例庫</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/cases/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/cases/</guid><description>&lt;p>本案例庫以服務為單位、收錄公開事故報告（post-mortem / status page / 工程部落格）。每個服務一個資料夾，累積該服務的架構脈絡、事故時間線與共通失敗模式。&lt;/p>
&lt;p>服務分層依 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">模組八 _index&lt;/a> 的 T1 / T2 / T3 規劃。重複出現於 06 / 08 的服務（stripe / cloudflare / linkedin）資料夾住在主要教學模組、跨模組以連結互通。&lt;/p>
&lt;h2 id="完成狀態">完成狀態&lt;/h2>
&lt;p>案例庫的完成狀態以「可直接引用的事故頁」為準。服務資料夾只算索引，子案例頁才算可引用素材；每篇子案例至少要有事故摘要、判讀訊號、事故路徑、可回寫控制面、下一步路由與引用源。&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>Cloudflare&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal&lt;/a>&lt;/td>
 &lt;td>已回寫 4.21 / 6.24&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS S3&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption&lt;/a>&lt;/td>
 &lt;td>補 2021 多服務退化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GitHub&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident&lt;/a>&lt;/td>
 &lt;td>補 2020 Actions 案例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>GCP&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident&lt;/a>&lt;/td>
 &lt;td>補 IAM 控制面案例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Atlassian&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage&lt;/a>&lt;/td>
 &lt;td>補次級事故對照&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Roblox&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage&lt;/a>&lt;/td>
 &lt;td>補恢復後優化案例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Fastly&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage&lt;/a>&lt;/td>
 &lt;td>補後續改善案例&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t2t3-第一批正文已完成">T2/T3 第一批正文（已完成）&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>Slack&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/" data-link-title="Slack：2022 連線恢復與狀態通訊節奏" data-link-desc="在通訊平台自身失效時，如何同步恢復節奏與對外狀態揭露。">SL1 連線恢復與狀態通訊&lt;/a>&lt;/td>
 &lt;td>通訊平台失效時的對外節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Datadog&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/" data-link-title="Datadog：2023 多區觀測中斷事件" data-link-desc="監控平台自身退化時，如何避免客戶誤判系統健康狀態。">DD1 多區觀測中斷&lt;/a>&lt;/td>
 &lt;td>監控平台失效的二階風險&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Discord&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">DC1 Gateway 容量事件&lt;/a>&lt;/td>
 &lt;td>長連線回復造成的二次擁塞&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Azure AD&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">AZ1 身分控制面中斷&lt;/a>&lt;/td>
 &lt;td>跨產品身份依賴分級治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Heroku&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">HR1 Routing 控制事件&lt;/a>&lt;/td>
 &lt;td>多租戶入口故障的局部化回復&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Reddit&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">RD1 Kubernetes 升級事故&lt;/a>&lt;/td>
 &lt;td>平台升級與回退決策治理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Microsoft 365&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/" data-link-title="Microsoft 365：套件級身分驗證事故" data-link-desc="企業套件在身份依賴失效時，如何同步處理跨產品影響與對外揭露。">M365-1 套件級身份事故&lt;/a>&lt;/td>
 &lt;td>企業套件跨產品影響盤點&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="t1-服務">T1 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/" data-link-title="AWS S3" data-link-desc="AWS S3 重大事故時間線與架構脈絡">aws-s3&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">cloudflare&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">github&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/" data-link-title="Google Cloud Platform" data-link-desc="GCP 重大事故時間線與架構脈絡">gcp&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/atlassian/" data-link-title="Atlassian" data-link-desc="Atlassian 多租戶事故時間線與架構脈絡">atlassian&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/roblox/" data-link-title="Roblox" data-link-desc="Roblox 73 小時事故時間線與架構脈絡">roblox&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/fastly/" data-link-title="Fastly" data-link-desc="Fastly 全球配置 push 事故時間線">fastly&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="t2-服務">T2 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/" data-link-title="Slack" data-link-desc="Slack 通訊服務事故與外部狀態頁設計">slack&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/datadog/" data-link-title="Datadog" data-link-desc="Datadog 監控服務事故、客戶觀測落差">datadog&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">stripe（住於 06）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/discord/" data-link-title="Discord" data-link-desc="Discord Gateway scale-out 事故與容量驚奇">discord&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/" data-link-title="Azure AD / Entra ID" data-link-desc="Microsoft Identity 控制面失效與 cascading 影響">azure-ad&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="t3-服務">T3 服務&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/heroku/" data-link-title="Heroku" data-link-desc="Heroku PaaS 事故與 router 層架構脈絡">heroku&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">linkedin（住於 06）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/reddit/" data-link-title="Reddit" data-link-desc="Reddit Pi Day 2023 k8s 升級事故">reddit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/microsoft-365/" data-link-title="Microsoft 365" data-link-desc="Microsoft 365 SaaS 套件事故與企業客戶影響">microsoft-365&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本案例庫以服務為單位、收錄公開事故報告（post-mortem / status page / 工程部落格）。每個服務一個資料夾，累積該服務的架構脈絡、事故時間線與共通失敗模式。</p>
<p>服務分層依 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">模組八 _index</a> 的 T1 / T2 / T3 規劃。重複出現於 06 / 08 的服務（stripe / cloudflare / linkedin）資料夾住在主要教學模組、跨模組以連結互通。</p>
<h2 id="完成狀態">完成狀態</h2>
<p>案例庫的完成狀態以「可直接引用的事故頁」為準。服務資料夾只算索引，子案例頁才算可引用素材；每篇子案例至少要有事故摘要、判讀訊號、事故路徑、可回寫控制面、下一步路由與引用源。</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>已完成案例</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cloudflare</td>
          <td><a href="/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">2019 Regex CPU Outage</a>、<a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">2023 Control Plane Token Incident</a>、<a href="/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">2026 BYOIP BGP Withdrawal</a></td>
          <td>已回寫 4.21 / 6.24</td>
      </tr>
      <tr>
          <td>AWS S3</td>
          <td><a href="/blog/backend/08-incident-response/cases/aws-s3/2017-us-east-1-service-disruption/" data-link-title="AWS S3 2017 US-EAST-1 Service Disruption" data-link-desc="2017-02-28 AWS S3 us-east-1 事故解析：內部操作命令、index / placement 子系統重啟、區域依賴擴散與狀態頁依賴回寫。">2017 US-EAST-1 Service Disruption</a></td>
          <td>補 2021 多服務退化</td>
      </tr>
      <tr>
          <td>GitHub</td>
          <td><a href="/blog/backend/08-incident-response/cases/github/2018-oct21-mysql-topology-incident/" data-link-title="GitHub 2018 Oct21 MySQL Topology Incident" data-link-desc="2018-10-21 GitHub 因 network partition 觸發跨區資料庫拓撲異常的事故解析：資料一致性優先、fail-forward 決策與長時間恢復。">2018 Oct21 MySQL Topology Incident</a></td>
          <td>補 2020 Actions 案例</td>
      </tr>
      <tr>
          <td>GCP</td>
          <td><a href="/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">2019 US Network Congestion Multi-service Incident</a></td>
          <td>補 IAM 控制面案例</td>
      </tr>
      <tr>
          <td>Atlassian</td>
          <td><a href="/blog/backend/08-incident-response/cases/atlassian/2022-april-multi-tenant-deletion-outage/" data-link-title="Atlassian 2022 April Multi-tenant Deletion Outage" data-link-desc="2022-04 Atlassian 因維運腳本誤刪多租戶站點造成長時間事故的解析：恢復分批、跨團隊指揮與對外通訊節奏。">2022 April Multi-tenant Deletion Outage</a></td>
          <td>補次級事故對照</td>
      </tr>
      <tr>
          <td>Roblox</td>
          <td><a href="/blog/backend/08-incident-response/cases/roblox/2021-oct-prolonged-core-infra-outage/" data-link-title="Roblox 2021 Oct Prolonged Core Infra Outage" data-link-desc="2021-10 Roblox 長時間平台中斷的事故解析：核心基礎設施壓力失衡、根因定位延遲與長尾恢復。">2021 Oct Prolonged Core Infra Outage</a></td>
          <td>補恢復後優化案例</td>
      </tr>
      <tr>
          <td>Fastly</td>
          <td><a href="/blog/backend/08-incident-response/cases/fastly/2021-june-global-edge-config-triggered-outage/" data-link-title="Fastly 2021 June Global Edge Config-triggered Outage" data-link-desc="2021-06-08 Fastly 全球 edge 事故解析：有效客戶配置觸發潛藏 bug、分鐘級擴散與快速隔離恢復。">2021 June Global Edge Config-triggered Outage</a></td>
          <td>補後續改善案例</td>
      </tr>
  </tbody>
</table>
<h2 id="t2t3-第一批正文已完成">T2/T3 第一批正文（已完成）</h2>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>正文入口</th>
          <th>主題重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Slack</td>
          <td><a href="/blog/backend/08-incident-response/cases/slack/2022-connection-recovery-and-status-communication/" data-link-title="Slack：2022 連線恢復與狀態通訊節奏" data-link-desc="在通訊平台自身失效時，如何同步恢復節奏與對外狀態揭露。">SL1 連線恢復與狀態通訊</a></td>
          <td>通訊平台失效時的對外節奏</td>
      </tr>
      <tr>
          <td>Datadog</td>
          <td><a href="/blog/backend/08-incident-response/cases/datadog/2023-multi-region-observability-disruption/" data-link-title="Datadog：2023 多區觀測中斷事件" data-link-desc="監控平台自身退化時，如何避免客戶誤判系統健康狀態。">DD1 多區觀測中斷</a></td>
          <td>監控平台失效的二階風險</td>
      </tr>
      <tr>
          <td>Discord</td>
          <td><a href="/blog/backend/08-incident-response/cases/discord/2022-gateway-capacity-event/" data-link-title="Discord：Gateway 容量事件與恢復節奏" data-link-desc="長連線平台在容量邊界被擊穿時，如何控制擴散並分批恢復。">DC1 Gateway 容量事件</a></td>
          <td>長連線回復造成的二次擁塞</td>
      </tr>
      <tr>
          <td>Azure AD</td>
          <td><a href="/blog/backend/08-incident-response/cases/azure-ad/2021-identity-control-plane-disruption/" data-link-title="Azure AD：2021 身分控制面中斷事件" data-link-desc="身分服務失效時，如何評估跨產品影響與收斂優先序。">AZ1 身分控制面中斷</a></td>
          <td>跨產品身份依賴分級治理</td>
      </tr>
      <tr>
          <td>Heroku</td>
          <td><a href="/blog/backend/08-incident-response/cases/heroku/2021-routing-control-event/" data-link-title="Heroku：Routing 控制事件與多租戶影響" data-link-desc="PaaS 路由層異常時，如何限制租戶擴散並維持可用通訊。">HR1 Routing 控制事件</a></td>
          <td>多租戶入口故障的局部化回復</td>
      </tr>
      <tr>
          <td>Reddit</td>
          <td><a href="/blog/backend/08-incident-response/cases/reddit/2023-kubernetes-upgrade-incident/" data-link-title="Reddit：2023 Kubernetes 升級事故" data-link-desc="平台升級變更如何觸發服務退化，以及如何設計可回退的升級策略。">RD1 Kubernetes 升級事故</a></td>
          <td>平台升級與回退決策治理</td>
      </tr>
      <tr>
          <td>Microsoft 365</td>
          <td><a href="/blog/backend/08-incident-response/cases/microsoft-365/2023-suite-wide-authentication-incident/" data-link-title="Microsoft 365：套件級身分驗證事故" data-link-desc="企業套件在身份依賴失效時，如何同步處理跨產品影響與對外揭露。">M365-1 套件級身份事故</a></td>
          <td>企業套件跨產品影響盤點</td>
      </tr>
  </tbody>
</table>
<h2 id="t1-服務">T1 服務</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/" data-link-title="AWS S3" data-link-desc="AWS S3 重大事故時間線與架構脈絡">aws-s3</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">cloudflare</a></li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">github</a></li>
<li><a href="/blog/backend/08-incident-response/cases/gcp/" data-link-title="Google Cloud Platform" data-link-desc="GCP 重大事故時間線與架構脈絡">gcp</a></li>
<li><a href="/blog/backend/08-incident-response/cases/atlassian/" data-link-title="Atlassian" data-link-desc="Atlassian 多租戶事故時間線與架構脈絡">atlassian</a></li>
<li><a href="/blog/backend/08-incident-response/cases/roblox/" data-link-title="Roblox" data-link-desc="Roblox 73 小時事故時間線與架構脈絡">roblox</a></li>
<li><a href="/blog/backend/08-incident-response/cases/fastly/" data-link-title="Fastly" data-link-desc="Fastly 全球配置 push 事故時間線">fastly</a></li>
</ul>
<h2 id="t2-服務">T2 服務</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/slack/" data-link-title="Slack" data-link-desc="Slack 通訊服務事故與外部狀態頁設計">slack</a></li>
<li><a href="/blog/backend/08-incident-response/cases/datadog/" data-link-title="Datadog" data-link-desc="Datadog 監控服務事故、客戶觀測落差">datadog</a></li>
<li><a href="/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">stripe（住於 06）</a></li>
<li><a href="/blog/backend/08-incident-response/cases/discord/" data-link-title="Discord" data-link-desc="Discord Gateway scale-out 事故與容量驚奇">discord</a></li>
<li><a href="/blog/backend/08-incident-response/cases/azure-ad/" data-link-title="Azure AD / Entra ID" data-link-desc="Microsoft Identity 控制面失效與 cascading 影響">azure-ad</a></li>
</ul>
<h2 id="t3-服務">T3 服務</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/heroku/" data-link-title="Heroku" data-link-desc="Heroku PaaS 事故與 router 層架構脈絡">heroku</a></li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">linkedin（住於 06）</a></li>
<li><a href="/blog/backend/08-incident-response/cases/reddit/" data-link-title="Reddit" data-link-desc="Reddit Pi Day 2023 k8s 升級事故">reddit</a></li>
<li><a href="/blog/backend/08-incident-response/cases/microsoft-365/" data-link-title="Microsoft 365" data-link-desc="Microsoft 365 SaaS 套件事故與企業客戶影響">microsoft-365</a></li>
</ul>
]]></content:encoded></item><item><title>模組九案例正文</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/</guid><description>&lt;p>這個資料夾的核心責任是把雲端服務商公開的高併發實戰案例轉成可回寫主章判讀的案例正文。資料來源以 &lt;a href="https://aws.amazon.com/solutions/case-studies/">AWS Customer Success Stories&lt;/a>、&lt;a href="https://cloud.google.com/customers">Google Cloud Customer Stories&lt;/a> 與 &lt;a href="https://customers.microsoft.com/">Azure Customer Case Studies&lt;/a> 為主，因為這層案例同時提供具體流量數字、實際使用的服務組合與工程決策路徑，比一般 engineering blog 更接近實戰判讀。&lt;/p>
&lt;p>跟模組七案例庫一樣、本資料夾不只服務 09 主章閱讀、也是 01-05 模組寫作時的證據來源。當寫 01 資料庫章節需要說明「Aurora 真實流量下能撐多少」、當寫 02 快取章節需要說明「ElastiCache 在持續成長服務的角色」時、可以直接回查本資料夾相應案例。&lt;/p>
&lt;h2 id="跟-06-案例庫的差異">跟 06 案例庫的差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases&lt;/a>&lt;/th>
 &lt;th>09 cases（本資料夾）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>來源&lt;/td>
 &lt;td>大企業工程部落格（Google SRE Book、Netflix Tech Blog、Shopify 等）&lt;/td>
 &lt;td>AWS / GCP / Azure 官方 customer case studies&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>證據型態&lt;/td>
 &lt;td>方法論敘事（SLO 政策、chaos hypothesis、failure mode）&lt;/td>
 &lt;td>具體流量、實例、延遲、成本數字（QPS、msg/sec、p95、cost ratio）&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>兩層案例互補。06 教讀者「怎麼預先驗證失敗會被擋住」、09 教讀者「實際配置在實際流量下會怎麼跑」。同一個服務可以同時出現在兩處、但讀法不同。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;p>每個案例標 tag 讓多個主章可以反查。tag 維度：&lt;strong>雲商&lt;/strong>（aws / gcp / azure）、&lt;strong>服務維度&lt;/strong>（db-oltp / db-kv / cache / mq-stream / compute / global-edge / latency / data-architecture）、&lt;strong>負載形狀&lt;/strong>（predictable-peak / event-peak / surge / flash-sale-spike / low-latency-sustained / sustained-growth）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>雲商&lt;/th>
 &lt;th>服務維度&lt;/th>
 &lt;th>負載形狀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1&lt;/a>&lt;/td>
 &lt;td>AWS Prime Day 2025 dogfood&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>multi&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2&lt;/a>&lt;/td>
 &lt;td>GR8 Tech 體育博彩 AI 預測式擴容&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3&lt;/a>&lt;/td>
 &lt;td>Coinbase 超低延遲交易&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>latency&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &amp;#43;50% 不影響延遲">9.C4&lt;/a>&lt;/td>
 &lt;td>DraftKings Aurora 100 萬 ops/min&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5&lt;/a>&lt;/td>
 &lt;td>Amazon Ads DynamoDB 9000 萬 RPS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6&lt;/a>&lt;/td>
 &lt;td>Tinder ElastiCache 配對引擎&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&amp;#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&amp;#43; 個微服務承載 8 倍峰值流量、跨 200&amp;#43; 城市">9.C7&lt;/a>&lt;/td>
 &lt;td>Lyft 100+ 微服務 8x 峰值&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8&lt;/a>&lt;/td>
 &lt;td>Niantic Pokémon GO 50x 突發&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9&lt;/a>&lt;/td>
 &lt;td>Spotify Kafka → Pub/Sub 遷移&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>mq-stream&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10&lt;/a>&lt;/td>
 &lt;td>Cloud Spanner 10 億 req/sec&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11&lt;/a>&lt;/td>
 &lt;td>Minecraft Earth Cosmos DB 全球&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12&lt;/a>&lt;/td>
 &lt;td>Riot Games 246 EKS clusters&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13&lt;/a>&lt;/td>
 &lt;td>Hotstar IPL 1860 萬同時觀看&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>global-edge&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14&lt;/a>&lt;/td>
 &lt;td>Standard Chartered Aurora 4000 TPS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15&lt;/a>&lt;/td>
 &lt;td>拓元 Tixcraft 售票搶購&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &amp;#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &amp;#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16&lt;/a>&lt;/td>
 &lt;td>SeatGeek Virtual Waiting Room&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17&lt;/a>&lt;/td>
 &lt;td>BookMyShow 印度年售 2 億張票&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18&lt;/a>&lt;/td>
 &lt;td>Zoom COVID 30x DAU 突發&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19&lt;/a>&lt;/td>
 &lt;td>Capcom 遊戲後端 DynamoDB + EKS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20&lt;/a>&lt;/td>
 &lt;td>Zomato TiDB → DynamoDB 4x 吞吐&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21&lt;/a>&lt;/td>
 &lt;td>ASOS Cosmos DB Black Friday&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&amp;#43; 商品 &amp;#43; 16,000&amp;#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22&lt;/a>&lt;/td>
 &lt;td>Wayfair GCP burst capacity&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23&lt;/a>&lt;/td>
 &lt;td>Netflix Aurora 統一 +75% 效能&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24&lt;/a>&lt;/td>
 &lt;td>Genesys 99.999% 跨 15 region&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25&lt;/a>&lt;/td>
 &lt;td>Tubi ML feature store sub-10ms p99&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26&lt;/a>&lt;/td>
 &lt;td>PayPay 行動支付每日 3 億訊息&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&amp;#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&amp;#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27&lt;/a>&lt;/td>
 &lt;td>Disney+ 觀看歷史每日數十億動作&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &amp;#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &amp;#43; Wavelength &amp;#43; Outposts 處理 20&amp;#43; 州的雙重峰值">9.C28&lt;/a>&lt;/td>
 &lt;td>FanDuel 直播 + 投注雙重峰值&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &amp;#43; AWS Media Services 撐 30 channels live &amp;#43; 5M MAU、工程工時下降 90%">9.C29&lt;/a>&lt;/td>
 &lt;td>NTT DOCOMO Lemino 5M MAU / 3 個月&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30&lt;/a>&lt;/td>
 &lt;td>Microsoft 365 MongoDB → Cosmos DB&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &amp;#43; 1.5 億商品、用 GCP Vertex AI Search &amp;#43; BigQuery 提供近即時搜尋與分析">9.C31&lt;/a>&lt;/td>
 &lt;td>Mercado Libre LatAm Vertex + BigQuery&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/" data-link-title="9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易" data-link-desc="Clearent 在 Azure SQL Hyperscale 上處理每年 5 億筆支付交易、autoscale &amp;#43; 微服務架構">9.C32&lt;/a>&lt;/td>
 &lt;td>Clearent Azure SQL Hyperscale 5 億 txn/年&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &amp;#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33&lt;/a>&lt;/td>
 &lt;td>Maersk + Bosch Azure AKS&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &amp;#43; 1000 Pods/sec 創建吞吐">9.C34&lt;/a>&lt;/td>
 &lt;td>GCP 130K-node GKE cluster (AI)&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/" data-link-title="9.C35 Snap：GCP &amp;#43; KeyDB 在 multi-cloud 架構下的低延遲快取" data-link-desc="Snap 用 GCP 上的 KeyDB cluster 減少跨 cloud cache 延遲、用 TPU 訓練廣告推薦模型">9.C35&lt;/a>&lt;/td>
 &lt;td>Snap GCP KeyDB cross-cloud cache&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &amp;#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36&lt;/a>&lt;/td>
 &lt;td>Coinbase MongoDB 1.5M reads/sec&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37&lt;/a>&lt;/td>
 &lt;td>Forbes 自管 MongoDB → Atlas on GCP&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38&lt;/a>&lt;/td>
 &lt;td>Toyota Connected MongoDB 月 180 億 txn&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="主章寫作時的反查路由">主章寫作時的反查路由&lt;/h2>
&lt;p>當寫 01-05 模組的具體服務章節需要援引「真實流量下會發生什麼」、查下表找對應案例。&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把雲端服務商公開的高併發實戰案例轉成可回寫主章判讀的案例正文。資料來源以 <a href="https://aws.amazon.com/solutions/case-studies/">AWS Customer Success Stories</a>、<a href="https://cloud.google.com/customers">Google Cloud Customer Stories</a> 與 <a href="https://customers.microsoft.com/">Azure Customer Case Studies</a> 為主，因為這層案例同時提供具體流量數字、實際使用的服務組合與工程決策路徑，比一般 engineering blog 更接近實戰判讀。</p>
<p>跟模組七案例庫一樣、本資料夾不只服務 09 主章閱讀、也是 01-05 模組寫作時的證據來源。當寫 01 資料庫章節需要說明「Aurora 真實流量下能撐多少」、當寫 02 快取章節需要說明「ElastiCache 在持續成長服務的角色」時、可以直接回查本資料夾相應案例。</p>
<h2 id="跟-06-案例庫的差異">跟 06 案例庫的差異</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th><a href="/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases</a></th>
          <th>09 cases（本資料夾）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>來源</td>
          <td>大企業工程部落格（Google SRE Book、Netflix Tech Blog、Shopify 等）</td>
          <td>AWS / GCP / Azure 官方 customer case studies</td>
      </tr>
      <tr>
          <td>證據型態</td>
          <td>方法論敘事（SLO 政策、chaos hypothesis、failure mode）</td>
          <td>具體流量、實例、延遲、成本數字（QPS、msg/sec、p95、cost ratio）</td>
      </tr>
      <tr>
          <td>讀法</td>
          <td>失敗模式如何被驗證</td>
          <td>容量量化實踐：什麼配置撐多少、加多少、成本曲線怎麼走</td>
      </tr>
      <tr>
          <td>教學責任</td>
          <td>把驗證流程制度化</td>
          <td>把容量地圖具體化、把成本邊界量化</td>
      </tr>
  </tbody>
</table>
<p>兩層案例互補。06 教讀者「怎麼預先驗證失敗會被擋住」、09 教讀者「實際配置在實際流量下會怎麼跑」。同一個服務可以同時出現在兩處、但讀法不同。</p>
<h2 id="案例列表">案例列表</h2>
<p>每個案例標 tag 讓多個主章可以反查。tag 維度：<strong>雲商</strong>（aws / gcp / azure）、<strong>服務維度</strong>（db-oltp / db-kv / cache / mq-stream / compute / global-edge / latency / data-architecture）、<strong>負載形狀</strong>（predictable-peak / event-peak / surge / flash-sale-spike / low-latency-sustained / sustained-growth）。</p>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>雲商</th>
          <th>服務維度</th>
          <th>負載形狀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1</a></td>
          <td>AWS Prime Day 2025 dogfood</td>
          <td>aws</td>
          <td>multi</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2</a></td>
          <td>GR8 Tech 體育博彩 AI 預測式擴容</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3</a></td>
          <td>Coinbase 超低延遲交易</td>
          <td>aws</td>
          <td>latency</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4</a></td>
          <td>DraftKings Aurora 100 萬 ops/min</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5</a></td>
          <td>Amazon Ads DynamoDB 9000 萬 RPS</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6</a></td>
          <td>Tinder ElastiCache 配對引擎</td>
          <td>aws</td>
          <td>cache</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7</a></td>
          <td>Lyft 100+ 微服務 8x 峰值</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8</a></td>
          <td>Niantic Pokémon GO 50x 突發</td>
          <td>gcp</td>
          <td>compute</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9</a></td>
          <td>Spotify Kafka → Pub/Sub 遷移</td>
          <td>gcp</td>
          <td>mq-stream</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a></td>
          <td>Cloud Spanner 10 億 req/sec</td>
          <td>gcp</td>
          <td>db-oltp</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11</a></td>
          <td>Minecraft Earth Cosmos DB 全球</td>
          <td>azure</td>
          <td>db-kv</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12</a></td>
          <td>Riot Games 246 EKS clusters</td>
          <td>aws</td>
          <td>compute</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13</a></td>
          <td>Hotstar IPL 1860 萬同時觀看</td>
          <td>aws</td>
          <td>global-edge</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14</a></td>
          <td>Standard Chartered Aurora 4000 TPS</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15</a></td>
          <td>拓元 Tixcraft 售票搶購</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16</a></td>
          <td>SeatGeek Virtual Waiting Room</td>
          <td>aws</td>
          <td>compute</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17</a></td>
          <td>BookMyShow 印度年售 2 億張票</td>
          <td>aws</td>
          <td>data-architecture</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18</a></td>
          <td>Zoom COVID 30x DAU 突發</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19</a></td>
          <td>Capcom 遊戲後端 DynamoDB + EKS</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20</a></td>
          <td>Zomato TiDB → DynamoDB 4x 吞吐</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21</a></td>
          <td>ASOS Cosmos DB Black Friday</td>
          <td>azure</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22</a></td>
          <td>Wayfair GCP burst capacity</td>
          <td>gcp</td>
          <td>data-architecture</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23</a></td>
          <td>Netflix Aurora 統一 +75% 效能</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24</a></td>
          <td>Genesys 99.999% 跨 15 region</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25</a></td>
          <td>Tubi ML feature store sub-10ms p99</td>
          <td>aws</td>
          <td>cache</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26</a></td>
          <td>PayPay 行動支付每日 3 億訊息</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27</a></td>
          <td>Disney+ 觀看歷史每日數十億動作</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &#43; Wavelength &#43; Outposts 處理 20&#43; 州的雙重峰值">9.C28</a></td>
          <td>FanDuel 直播 + 投注雙重峰值</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">9.C29</a></td>
          <td>NTT DOCOMO Lemino 5M MAU / 3 個月</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30</a></td>
          <td>Microsoft 365 MongoDB → Cosmos DB</td>
          <td>azure</td>
          <td>data-architecture</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31</a></td>
          <td>Mercado Libre LatAm Vertex + BigQuery</td>
          <td>gcp</td>
          <td>data-architecture</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/" data-link-title="9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易" data-link-desc="Clearent 在 Azure SQL Hyperscale 上處理每年 5 億筆支付交易、autoscale &#43; 微服務架構">9.C32</a></td>
          <td>Clearent Azure SQL Hyperscale 5 億 txn/年</td>
          <td>azure</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33</a></td>
          <td>Maersk + Bosch Azure AKS</td>
          <td>azure</td>
          <td>compute</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34</a></td>
          <td>GCP 130K-node GKE cluster (AI)</td>
          <td>gcp</td>
          <td>compute</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/" data-link-title="9.C35 Snap：GCP &#43; KeyDB 在 multi-cloud 架構下的低延遲快取" data-link-desc="Snap 用 GCP 上的 KeyDB cluster 減少跨 cloud cache 延遲、用 TPU 訓練廣告推薦模型">9.C35</a></td>
          <td>Snap GCP KeyDB cross-cloud cache</td>
          <td>gcp</td>
          <td>cache</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36</a></td>
          <td>Coinbase MongoDB 1.5M reads/sec</td>
          <td>aws</td>
          <td>db-document</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37</a></td>
          <td>Forbes 自管 MongoDB → Atlas on GCP</td>
          <td>gcp</td>
          <td>db-document</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38</a></td>
          <td>Toyota Connected MongoDB 月 180 億 txn</td>
          <td>aws</td>
          <td>db-document</td>
          <td>sustained-growth</td>
      </tr>
  </tbody>
</table>
<h2 id="主章寫作時的反查路由">主章寫作時的反查路由</h2>
<p>當寫 01-05 模組的具體服務章節需要援引「真實流量下會發生什麼」、查下表找對應案例。</p>
<h3 id="寫-01-資料庫模組-時">寫 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>OLTP 高 TPS 容量</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a> / <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></td>
      </tr>
      <tr>
          <td>KV 極高吞吐</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> / <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS</a></td>
      </tr>
      <tr>
          <td>全球一致性 OLTP</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（multi-region active-active）</td>
      </tr>
      <tr>
          <td>Transaction boundary</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a>（RAFT、強順序）</td>
      </tr>
      <tr>
          <td>Hot partition / 分片</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> / <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a></td>
      </tr>
      <tr>
          <td>DB 作為寫入緩衝</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（DynamoDB 緩衝 + 傳統 server 慢速消費）</td>
      </tr>
      <tr>
          <td>DB 種類整合 / consolidation</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys DynamoDB 為預設</a></td>
      </tr>
      <tr>
          <td>Migration 與合規</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> / <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> / <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato TiDB → DynamoDB</a> / <a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37 Forbes 自管 MongoDB → Atlas</a></td>
      </tr>
      <tr>
          <td>多事件 ticketing 資料層</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a></td>
      </tr>
      <tr>
          <td>Document database / MongoDB</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36 Coinbase</a>（1.5M reads/sec、connection proxy）/ <a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37 Forbes</a>（自管 → Atlas）/ <a href="/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38 Toyota Connected</a>（IoT telematics）/ <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-02-快取模組-時">寫 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>高吞吐 cache layer</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder</a></td>
      </tr>
      <tr>
          <td>Cache as SoT</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder</a>（配對快取為主要服務面）</td>
      </tr>
      <tr>
          <td>ML feature store</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi</a>（sub-10ms p99）</td>
      </tr>
      <tr>
          <td>Sub-ms latency 需求</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a>（不只 cache、整體 sub-ms 設計）</td>
      </tr>
      <tr>
          <td>Cache stampede</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokémon GO surge</a>（50x 突發必觸 stampede 風險）</td>
      </tr>
      <tr>
          <td>Cache hierarchy / 多層 cache</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi</a>（L1 in-process + L2 cache + L3 store）</td>
      </tr>
      <tr>
          <td>Cache vs durable store 取捨</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi</a>（從 ScyllaDB 遷到 ElastiCache）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-03-訊息佇列模組-時">寫 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大規模事件交付</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
      <tr>
          <td>Broker 自管 vs managed</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
      <tr>
          <td>極端 message volume</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 AWS Prime Day</a>（SQS 1.66 億 msg/sec）</td>
      </tr>
      <tr>
          <td>Queue 作為緩衝吸收洪峰</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（DynamoDB 模仿 queue 行為）</td>
      </tr>
      <tr>
          <td>Migration playbook</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
  </tbody>
</table>
<h3 id="寫-04-可觀測性模組-時">寫 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO 量測 baseline</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a>（99.999% availability）/ <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（99.999% 12 個月達成）</td>
      </tr>
      <tr>
          <td>Latency budget 反推</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi</a>（ML p99 分解）</td>
      </tr>
      <tr>
          <td>Saturation 訊號</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a>（25ms p95 是業務 KPI）</td>
      </tr>
      <tr>
          <td>多地區 metric 治理</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（15 主 region）</td>
      </tr>
      <tr>
          <td>SLO 演進 / surge 後校準</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a>（30x 後 baseline 永久上移）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-05-部署平台模組-時">寫 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>K8s multi-cluster</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a>（多遊戲共用 vs 多 cluster）</td>
      </tr>
      <tr>
          <td>Container vs VM</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokémon GO</a></td>
      </tr>
      <tr>
          <td>微服務切分</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a>（微服務私有 store）</td>
      </tr>
      <tr>
          <td>Autoscaling 策略</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 Prime Day</a> / <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> / <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（30 分鐘擴 130 倍）</td>
      </tr>
      <tr>
          <td>Global edge / CDN</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar</a> / <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（CloudFront 卸載靜態）</td>
      </tr>
      <tr>
          <td>限流 / Virtual Waiting Room</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek</a>（明確排隊）/ <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（隱性緩衝）</td>
      </tr>
      <tr>
          <td>Hybrid cloud / burst</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a>（on-prem + GCP burst）</td>
      </tr>
      <tr>
          <td>Control plane vs Data plane</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a>（DynamoDB 撐 metadata、影音另走 edge）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-00-服務選型模組-時">寫 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Traffic / data scale</td>
          <td>全部案例都可作對標、特別是 <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1</a> / <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a></td>
      </tr>
      <tr>
          <td>合規 / 受監管</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a></td>
      </tr>
      <tr>
          <td>Vendor 戰略支援</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokémon GO</a>（Google CRE）</td>
      </tr>
      <tr>
          <td>成本曲線</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a>（$10M 年省）</td>
      </tr>
  </tbody>
</table>
<h2 id="按負載形狀的讀法引導">按負載形狀的讀法引導</h2>
<p>當讀者遇到具體容量問題卡住時、先判斷負載屬於哪一種形狀、再選對應案例。</p>
<ol>
<li><strong>可預期極端峰值</strong>（年度活動、預售、賽事決賽）→ <a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 Prime Day</a> / <a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a></li>
<li><strong>事件型不可預期峰值</strong>（賽事高潮、突發新聞、KOL 推廣）→ <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> / <a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings</a> / <a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft</a></li>
<li><strong>突發遠超預期的 surge</strong>（產品爆紅、病毒式擴散、結構性外部事件）→ <a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokémon GO</a>（產品爆紅、暫時）/ <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom</a>（COVID 結構性永久）</li>
<li><strong>flash-sale 瞬間爆量</strong>（售票開賣、報名活動、限量搶購）→ <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft</a>（隱性緩衝）/ <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek</a>（明確排隊）/ <a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow</a>（規模化平台資料層）</li>
<li><strong>持續成長 sustained</strong>（用戶月增、業務擴張）→ <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads</a> / <a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder</a> / <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> / <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> / <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></li>
<li><strong>低延遲持續需求</strong>（金融交易、即時配對、廣告競價、ML inference）→ <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> / <a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi</a></li>
</ol>
<h3 id="surge-形狀的兩種次分類">surge 形狀的兩種次分類</h3>
<p>surge（突發遠超預期）內部還可分兩種、設計回應完全不同：</p>
<ul>
<li><strong>產品爆紅 surge</strong>（<a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Pokémon GO</a>）：流量隨熱度消退、是「暫時偏離 baseline 又回歸」。容量規劃焦點是「撐過熱度高峰、避免在最忙時掛」。</li>
<li><strong>結構性 surge</strong>（<a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom COVID</a>）：baseline 永久上移、是「新常態」。容量規劃焦點是「30x 後 SLO baseline 重新校準、長期成本曲線重算」。</li>
</ul>
<h3 id="flash-sale-spike-形狀的特殊性">flash-sale-spike 形狀的特殊性</h3>
<p>售票搶購 / 報名活動 / 限量搶購跟其他「峰值」案例有本質差異：</p>
<ul>
<li><strong>時間點精確、可秒級預測</strong>：開賣時刻 = 公告時刻、跟 GR8 Tech 的「賽事高潮」不一樣（賽事高潮在何時 + 多大都未知）</li>
<li><strong>持續時間極短</strong>：5-30 分鐘賣完、跟 Prime Day（48 小時）/ Hotstar IPL（4 小時）量級差很多</li>
<li><strong>峰值倍數極端</strong>：t=0 前流量近 0、t=0 瞬間衝到 10K-100K 倍、平均流量沒意義、只有峰值</li>
<li><strong>後端不容易跟上</strong>：高流量湧入時、付款 / 簽證 / 庫存後端通常是 legacy 系統、無法等比擴容、必須靠 buffer / queue / waiting room 解耦</li>
</ul>
<p>這個負載形狀的兩個主要設計模式：<strong>隱性緩衝</strong>（<a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">Tixcraft 模式</a>：用 DynamoDB / Kafka 吸收洪峰、後端慢消費）跟<strong>明確排隊</strong>（<a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">SeatGeek 模式</a>：Virtual Waiting Room + token-based queue）。實務常見組合使用 — 入口先排隊、進入後仍用 buffer。</p>
<h2 id="案例覆蓋矩陣">案例覆蓋矩陣</h2>
<p>下表顯示 38 個案例在 <em>服務維度 × 雲商</em> 的覆蓋情況、空格代表待補。</p>
<table>
  <thead>
      <tr>
          <th>服務維度</th>
          <th>AWS</th>
          <th>GCP</th>
          <th>Azure</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DB-OLTP</td>
          <td>C4, C14, C23</td>
          <td>C10</td>
          <td>C32</td>
      </tr>
      <tr>
          <td>DB-KV</td>
          <td>C5, C15, C18, C19, C20, C24, C26, C27, C29</td>
          <td>（待補）</td>
          <td>C11, C21</td>
      </tr>
      <tr>
          <td>DB-Document</td>
          <td>C36, C38</td>
          <td>C37</td>
          <td>（透過 C30 對照）</td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>C6, C25</td>
          <td>C35</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>MQ-Stream</td>
          <td>C1 (SQS), C7 (Kinesis)</td>
          <td>C9</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Compute / K8s</td>
          <td>C2, C7, C12, C16, C19, C28</td>
          <td>C8, C34</td>
          <td>C33</td>
      </tr>
      <tr>
          <td>Global Edge</td>
          <td>C13</td>
          <td>（待補）</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Latency 敏感</td>
          <td>C3, C25, C36</td>
          <td>C10, C35</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Data Architecture</td>
          <td>C17</td>
          <td>C22, C31</td>
          <td>C30</td>
      </tr>
  </tbody>
</table>
<p>AWS 25 個 case、GCP 8 個 case（補了 130K-node GKE + Snap KeyDB + Forbes）、Azure 5 個 case。三家覆蓋更平衡。新增 DB-Document 維度後、MongoDB 作為主角的案例（C36 Coinbase / C37 Forbes / C38 Toyota Connected）跟原本 C30 Microsoft 365（MongoDB 遷出 → Cosmos DB）形成完整 document model 案例組。剩餘缺口：Azure cache / global edge / latency、GCP DB-KV / MQ-Stream 加深、GCP / Azure global edge。</p>
<h3 id="負載形狀--雲商-覆蓋">負載形狀 × 雲商 覆蓋</h3>
<table>
  <thead>
      <tr>
          <th>負載形狀</th>
          <th>AWS</th>
          <th>GCP</th>
          <th>Azure</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>predictable-peak</td>
          <td>C1, C13, C27, C29</td>
          <td>C22</td>
          <td>C21</td>
      </tr>
      <tr>
          <td>event-peak</td>
          <td>C2, C4, C7, C28</td>
          <td>-</td>
          <td>-</td>
      </tr>
      <tr>
          <td>surge</td>
          <td>C18</td>
          <td>C8</td>
          <td>C11</td>
      </tr>
      <tr>
          <td>flash-sale-spike</td>
          <td>C15, C16, C17</td>
          <td>-</td>
          <td>-</td>
      </tr>
      <tr>
          <td>low-latency-sustained</td>
          <td>C3, C12, C24, C25, C36</td>
          <td>C10, C34, C35</td>
          <td>-</td>
      </tr>
      <tr>
          <td>sustained-growth</td>
          <td>C5, C6, C14, C19, C20, C23, C26, C38</td>
          <td>C9, C31, C37</td>
          <td>C30, C32, C33</td>
      </tr>
  </tbody>
</table>
<p>flash-sale-spike 是 09 案例庫的核心 differentiator — 雲商案例庫對這個負載形狀的著墨遠勝一般 engineering blog。surge 維度補了 Zoom 之後、跟 Pokemon GO（暫時 surge）跟 Minecraft Earth（地理 surge）形成三種次分類對照。後續若有 GCP / Azure 同類售票案例可補。</p>
<h2 id="規劃中案例第二批">規劃中案例（第二批）</h2>
<p>待 09 主章寫作推進、第二批案例可從下列候選補齊。</p>
<table>
  <thead>
      <tr>
          <th>候選案例</th>
          <th>預期教學重點</th>
          <th>來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disney+ DynamoDB</td>
          <td>每日數十億動作、watch list metadata</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>PayPay 30 億訊息/日</td>
          <td>行動支付的持續高頻 message</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Capcom DynamoDB</td>
          <td>遊戲業數十億請求、single-digit ms</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Zomato 90% 延遲下降</td>
          <td>帳務處理、跨資料庫遷移效益</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Zoom COVID 30x 成長</td>
          <td>1000 萬 → 3 億 DAU、突發長期 sustained</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>FanFight 100 萬寫入/秒</td>
          <td>印度 fantasy sports 體育博彩</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Tubi ScyllaDB → ElastiCache</td>
          <td>ML feature store sub-10ms p99</td>
          <td><a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers</a></td>
      </tr>
      <tr>
          <td>FanDuel 直播 + 投注</td>
          <td>雙重峰值對齊</td>
          <td><a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel case study</a></td>
      </tr>
      <tr>
          <td>Blockchain.com Spanner</td>
          <td>Crypto 高頻交易、強一致全球</td>
          <td><a href="https://cloud.google.com/blog/products/databases/using-cloud-spanner-to-handle-high-throughput-writes/">Spanner blog</a></td>
      </tr>
      <tr>
          <td>Walmart Cosmos DB</td>
          <td>全球零售 KV、跨地區一致性策略</td>
          <td><a href="https://azure.microsoft.com/en-us/blog/azure-cosmos-db-pushing-the-frontier-of-globally-distributed-databases/">Cosmos DB blog</a></td>
      </tr>
      <tr>
          <td>Microsoft 365 Cosmos</td>
          <td>MongoDB → Cosmos 遷移、planet-scale 分析</td>
          <td><a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Cosmos DB Microsoft 365 blog</a></td>
      </tr>
  </tbody>
</table>
<h2 id="engineering-blog-補充候選">Engineering Blog 補充候選</h2>
<p>當 AWS / GCP / Azure 案例缺乏某些工程紀律的深度（例如 chaos hypothesis、cell-based architecture 細節），補引 engineering blog 作為交叉驗證。候選來源：Shopify BFCM、Netflix Tech Blog、Amazon Builders&rsquo; Library、Google SRE Book、LinkedIn Engineering、Stripe Engineering、Cloudflare Blog、Discord Engineering、Uber Engineering、Pinterest Engineering 等。這層不另開資料夾、補在主章「案例對照」段。</p>
<h2 id="案例正文格式">案例正文格式</h2>
<p>每篇案例使用統一結構、方便快速比對。</p>
<ol>
<li><strong>觀察</strong>：客觀數字與事件序列。流量規模、實例配置、延遲分布、成本變化都用引用源的原始數字、不四捨五入。</li>
<li><strong>判讀</strong>：把案例的工程決策翻成主章的問題節點。</li>
<li><strong>策略</strong>：可重用的工程做法、去掉雲端 vendor 特異性。EKS、Auto Scaling、DynamoDB on-demand 等翻成跨平台等效概念。</li>
<li><strong>下一步路由</strong>：往哪個主章或前置案例延伸閱讀。</li>
<li><strong>引用源</strong>：雲端服務商官方 case study URL + 相關 Architecture Blog 連結。</li>
</ol>
<h2 id="tripwire">Tripwire</h2>
<ul>
<li>同一服務維度的 case 超過 5 個時、暫停擴張、改補其他維度。</li>
<li>AWS 案例數字過於行銷、缺工程細節 → 補 AWS Architecture Blog 同主題文章作為交叉驗證。</li>
<li>案例只是「我們用了 X 服務」、沒有具體量化結果 → 不收進案例庫、作為候選參考即可。</li>
<li>同一公司多個案例（例如 Coinbase 還有遷移案例）→ 拆 sub-case 而不是合成單一檔。</li>
<li>GCP / Azure 覆蓋持續落後 AWS 超過 2 倍時 → 主動補 GCP / Azure 案例、不要讓案例庫變成 AWS-only。</li>
</ul>
]]></content:encoded></item><item><title>4.x Hands-on：端到端案例</title><link>https://tarrragon.github.io/blog/llm/04-applications/hands-on/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/hands-on/</guid><description>&lt;p>本子資料夾收錄把模組四原理串起來的端到端案例。跟前面 principle-first 章節的差別：principle 章節是「跨工具不變的原理」、hands-on 是「把這些原理放在同一個任務上、走一遍完整流程」。&lt;/p>
&lt;p>讀法建議：先讀 principle 章節建立心智模型、再進 hands-on 看「實際做的時候、原理怎麼落」。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/llm/04-applications/hands-on/customer-support-case-study/" data-link-title="Case Study：customer support agent 從 task decomposition 到 eval" data-link-desc="把模組四原理串成端到端案例：observe → decompose → design workflow → instrument trace → design eval → iterate。每段標出引用哪章。">Customer support agent 從零到 eval&lt;/a>&lt;/td>
 &lt;td>Task decomposition → 設計 → trace → eval → iterate&lt;/td>
 &lt;td>4.0 prompt / 4.1 RAG / 4.3 tool / 4.4 agent / 4.5 HITL / 4.7 workflow / 4.13 eval / 4.20 trace&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/hands-on/blog-vector-search/" data-link-title="Case Study：Blog 語意搜尋從 pickle 到 production" data-link-desc="為 CLI 或個人工具選 RAG storage backend、或原始選型理由被 benchmark 推翻但結論不變時，如何區分結論、理由與前提">Blog 語意搜尋從 pickle 到 production&lt;/a>&lt;/td>
 &lt;td>Storage 選型 → 實作 → 效能優化 → 四方案 benchmark&lt;/td>
 &lt;td>4.1 RAG / 4.12 embedding / 4.14 benchmarking / 4.22 storage 工程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<p>本子資料夾收錄把模組四原理串起來的端到端案例。跟前面 principle-first 章節的差別：principle 章節是「跨工具不變的原理」、hands-on 是「把這些原理放在同一個任務上、走一遍完整流程」。</p>
<p>讀法建議：先讀 principle 章節建立心智模型、再進 hands-on 看「實際做的時候、原理怎麼落」。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>主題</th>
          <th>對應原理章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/04-applications/hands-on/customer-support-case-study/" data-link-title="Case Study：customer support agent 從 task decomposition 到 eval" data-link-desc="把模組四原理串成端到端案例：observe → decompose → design workflow → instrument trace → design eval → iterate。每段標出引用哪章。">Customer support agent 從零到 eval</a></td>
          <td>Task decomposition → 設計 → trace → eval → iterate</td>
          <td>4.0 prompt / 4.1 RAG / 4.3 tool / 4.4 agent / 4.5 HITL / 4.7 workflow / 4.13 eval / 4.20 trace</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/04-applications/hands-on/blog-vector-search/" data-link-title="Case Study：Blog 語意搜尋從 pickle 到 production" data-link-desc="為 CLI 或個人工具選 RAG storage backend、或原始選型理由被 benchmark 推翻但結論不變時，如何區分結論、理由與前提">Blog 語意搜尋從 pickle 到 production</a></td>
          <td>Storage 選型 → 實作 → 效能優化 → 四方案 benchmark</td>
          <td>4.1 RAG / 4.12 embedding / 4.14 benchmarking / 4.22 storage 工程</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>案例研究</title><link>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/</guid><description>&lt;p>本節收錄基於 &lt;code>.claude/lib&lt;/code> 實際程式碼的案例研究，展示如何應用 CPython 內部機制知識進行效能分析與優化。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/profiling/" data-link-title="案例：效能分析實戰" data-link-desc="用 cProfile 和 line_profiler 分析 Markdown 連結檢查器的效能瓶頸">效能分析實戰&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>cProfile、line_profiler、效能瓶頸定位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/" data-link-title="案例：記憶體優化" data-link-desc="用 __slots__ 和 weakref 優化快取系統的記憶體使用">記憶體優化&lt;/a>&lt;/td>
 &lt;td>config_loader.py&lt;/td>
 &lt;td>&lt;strong>slots&lt;/strong>、weakref、記憶體佔用分析&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>
&lt;p>建議先完成本模組的理論章節，再閱讀案例研究：&lt;/p>
&lt;ol>
&lt;li>理解 CPython 的物件模型&lt;/li>
&lt;li>學習效能分析工具&lt;/li>
&lt;li>通過案例實踐優化技巧&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>本節收錄基於 <code>.claude/lib</code> 實際程式碼的案例研究，展示如何應用 CPython 內部機制知識進行效能分析與優化。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>學習重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/04-cpython-internals/case-studies/profiling/" data-link-title="案例：效能分析實戰" data-link-desc="用 cProfile 和 line_profiler 分析 Markdown 連結檢查器的效能瓶頸">效能分析實戰</a></td>
          <td>markdown_link_checker.py</td>
          <td>cProfile、line_profiler、效能瓶頸定位</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/" data-link-title="案例：記憶體優化" data-link-desc="用 __slots__ 和 weakref 優化快取系統的記憶體使用">記憶體優化</a></td>
          <td>config_loader.py</td>
          <td><strong>slots</strong>、weakref、記憶體佔用分析</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>
<p>建議先完成本模組的理論章節，再閱讀案例研究：</p>
<ol>
<li>理解 CPython 的物件模型</li>
<li>學習效能分析工具</li>
<li>通過案例實踐優化技巧</li>
</ol>
]]></content:encoded></item><item><title>案例研究</title><link>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/</guid><description>&lt;p>本節收錄基於 &lt;code>.claude/lib&lt;/code> 實際程式碼的案例研究，展示如何用 Cython 加速 Python 程式碼。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/" data-link-title="案例：Cython 加速 Markdown 解析" data-link-desc="用 Cython 加速 Markdown 連結解析器，比較純 Python 與 Cython 的效能差異">Cython 加速 Markdown 解析&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>Cython 基礎、型別宣告、效能比較&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/05-c-extensions/case-studies/ctypes-system-call/" data-link-title="案例：使用 ctypes 呼叫系統 API" data-link-desc="透過 ctypes 直接呼叫 C 函式庫的系統函式，實現 Python 標準庫未提供的功能">使用 ctypes 呼叫系統 API&lt;/a>&lt;/td>
 &lt;td>系統 API&lt;/td>
 &lt;td>ctypes 實戰、跨平台、效能比較&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>
&lt;p>建議先完成本模組的理論章節，再閱讀案例研究：&lt;/p>
&lt;ol>
&lt;li>理解 Python/C API 基礎&lt;/li>
&lt;li>學習 Cython 語法&lt;/li>
&lt;li>通過案例實踐加速技巧&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>本節收錄基於 <code>.claude/lib</code> 實際程式碼的案例研究，展示如何用 Cython 加速 Python 程式碼。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>學習重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/05-c-extensions/case-studies/cython-markdown/" data-link-title="案例：Cython 加速 Markdown 解析" data-link-desc="用 Cython 加速 Markdown 連結解析器，比較純 Python 與 Cython 的效能差異">Cython 加速 Markdown 解析</a></td>
          <td>markdown_link_checker.py</td>
          <td>Cython 基礎、型別宣告、效能比較</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/05-c-extensions/case-studies/ctypes-system-call/" data-link-title="案例：使用 ctypes 呼叫系統 API" data-link-desc="透過 ctypes 直接呼叫 C 函式庫的系統函式，實現 Python 標準庫未提供的功能">使用 ctypes 呼叫系統 API</a></td>
          <td>系統 API</td>
          <td>ctypes 實戰、跨平台、效能比較</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>
<p>建議先完成本模組的理論章節，再閱讀案例研究：</p>
<ol>
<li>理解 Python/C API 基礎</li>
<li>學習 Cython 語法</li>
<li>通過案例實踐加速技巧</li>
</ol>
]]></content:encoded></item><item><title>案例研究</title><link>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/</guid><description>&lt;p>本節收錄基於 &lt;code>.claude/lib&lt;/code> 實際程式碼的案例研究，展示如何用 PyO3 和 Rust 加速 Python 程式碼。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/" data-link-title="案例：PyO3 文字解析" data-link-desc="用 PyO3 和 Rust 實現高效能的 Markdown 連結解析器">PyO3 文字解析&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>PyO3 基礎、Maturin 建置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/06-rust-extensions/case-studies/rust-regex/" data-link-title="案例：Rust 正則表達式" data-link-desc="用 Rust regex crate 加速 Hook 驗證器的模式匹配">Rust 正則表達式&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>regex crate、效能比較&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>
&lt;p>建議先完成本模組的理論章節，再閱讀案例研究：&lt;/p>
&lt;ol>
&lt;li>理解 Rust 基礎語法&lt;/li>
&lt;li>學習 PyO3 bindings&lt;/li>
&lt;li>通過案例實踐 Rust 加速&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>本節收錄基於 <code>.claude/lib</code> 實際程式碼的案例研究，展示如何用 PyO3 和 Rust 加速 Python 程式碼。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>學習重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/06-rust-extensions/case-studies/pyo3-parser/" data-link-title="案例：PyO3 文字解析" data-link-desc="用 PyO3 和 Rust 實現高效能的 Markdown 連結解析器">PyO3 文字解析</a></td>
          <td>markdown_link_checker.py</td>
          <td>PyO3 基礎、Maturin 建置</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/06-rust-extensions/case-studies/rust-regex/" data-link-title="案例：Rust 正則表達式" data-link-desc="用 Rust regex crate 加速 Hook 驗證器的模式匹配">Rust 正則表達式</a></td>
          <td>hook_validator.py</td>
          <td>regex crate、效能比較</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>
<p>建議先完成本模組的理論章節，再閱讀案例研究：</p>
<ol>
<li>理解 Rust 基礎語法</li>
<li>學習 PyO3 bindings</li>
<li>通過案例實踐 Rust 加速</li>
</ol>
]]></content:encoded></item><item><title>案例研究</title><link>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/</guid><description>&lt;p>本節收錄基於 &lt;code>.claude/lib&lt;/code> 實際程式碼的案例研究，展示如何將內部共用庫打包成可重用的 Python 套件。&lt;/p>
&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;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/case-studies/package-library/" data-link-title="案例：打包共用庫" data-link-desc="將 .claude/lib 打包成可重用的 Python 套件">打包共用庫&lt;/a>&lt;/td>
 &lt;td>整個 lib 目錄&lt;/td>
 &lt;td>pyproject.toml、版本管理、發布流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>
&lt;p>建議先完成本模組的理論章節，再閱讀案例研究：&lt;/p>
&lt;ol>
&lt;li>理解現代 Python 打包標準&lt;/li>
&lt;li>學習 pyproject.toml 設定&lt;/li>
&lt;li>通過案例實踐打包流程&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>本節收錄基於 <code>.claude/lib</code> 實際程式碼的案例研究，展示如何將內部共用庫打包成可重用的 Python 套件。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>學習重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/07-packaging/case-studies/package-library/" data-link-title="案例：打包共用庫" data-link-desc="將 .claude/lib 打包成可重用的 Python 套件">打包共用庫</a></td>
          <td>整個 lib 目錄</td>
          <td>pyproject.toml、版本管理、發布流程</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>
<p>建議先完成本模組的理論章節，再閱讀案例研究：</p>
<ol>
<li>理解現代 Python 打包標準</li>
<li>學習 pyproject.toml 設定</li>
<li>通過案例實踐打包流程</li>
</ol>
]]></content:encoded></item><item><title>7.R7 事故案例庫（可引用）</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/</guid><description>&lt;p>這個分類的責任是把事故拆成可重複引用的決策素材。每篇案例都用同一組結構回答：事故摘要 + 演示焦點、攻擊路徑、失效控制面、少一步的後果、可落地的 workflow 檢查點、從本案例到實作的 chain、可追溯來源。&lt;/p>
&lt;h2 id="分類入口依-threat-surface-分軸">分類入口（依 threat surface 分軸）&lt;/h2>
&lt;p>四個子分類各自承擔一條 threat surface 的演示，避免單一 case 嘗試涵蓋所有威脅面。讀者依當下要分析的 threat 類型選分類入口：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>分類&lt;/th>
 &lt;th>演示的 threat surface&lt;/th>
 &lt;th>典型 chain anchor&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/" data-link-title="7.R7.1 Identity &amp;amp; Access 類案例" data-link-desc="整理身分流程、社交工程、支援系統與 token 鏈的事故案例">Identity &amp;amp; Access&lt;/a>&lt;/td>
 &lt;td>身分、認證流程、社交工程、第三方身分鏈&lt;/td>
 &lt;td>7.2 身分與授權邊界 + 7.5 federated trust&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/" data-link-title="7.R7.2 Supply Chain 類案例" data-link-desc="整理第三方整合、CI/CD、更新鏈、開源與 MSP 供應鏈事故案例">Supply Chain&lt;/a>&lt;/td>
 &lt;td>CI/CD、更新鏈、RMM、開源與 MSP 供應鏈&lt;/td>
 &lt;td>7.6 供應鏈完整性與 artifact 信任&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/" data-link-title="7.R7.3 Edge Exposure 類案例" data-link-desc="整理邊界設備、外網入口、管理平面與鏈式漏洞事故案例">Edge Exposure&lt;/a>&lt;/td>
 &lt;td>邊界設備、外網入口、管理平面、鏈式漏洞&lt;/td>
 &lt;td>7.3 入口與伺服端保護 + 7.12 偵測涵蓋&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/" data-link-title="7.R7.4 Data Exfiltration 類案例" data-link-desc="整理資料外送、備份風險、勒索回復與營運衝擊相關事故案例">Data Exfiltration&lt;/a>&lt;/td>
 &lt;td>資料外送、備份鏈、營運中斷與回復壓力&lt;/td>
 &lt;td>7.9 資料保護 + 7.10 資料 residency + recovery readiness&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跨分類引用時、以同 threat surface 的 case 互相 link 為主、跨 surface 視為 multi-vector 案例（如 MGM 同時在 identity-access 與 ops-impact）、需在演示焦點段明示。&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/case-reference-map/" data-link-title="7.R7.M 案例引用地圖（服務主題 -&amp;gt; 案例 -&amp;gt; workflow）" data-link-desc="把服務主題連到完整案例體系，再連回 incident workflow 檢查點">案例引用地圖&lt;/a>：服務主題到案例與 workflow 檢查點的對照。&lt;/li>
&lt;/ul>
&lt;h2 id="使用方式">使用方式&lt;/h2>
&lt;ol>
&lt;li>先在服務章節定義風險主題與控制面。&lt;/li>
&lt;li>選一篇同 threat surface 的 case、引用「如果 workflow 少一步會發生什麼」+「演示焦點」。&lt;/li>
&lt;li>沿 case 的「從本案例到實作的 chain」走到對應 problem-card（若有）/ 主章節點 / blue-team scenario。&lt;/li>
&lt;li>把該步驟落地到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow&lt;/a> 的 runbook 流程。&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>這個分類的責任是把事故拆成可重複引用的決策素材。每篇案例都用同一組結構回答：事故摘要 + 演示焦點、攻擊路徑、失效控制面、少一步的後果、可落地的 workflow 檢查點、從本案例到實作的 chain、可追溯來源。</p>
<h2 id="分類入口依-threat-surface-分軸">分類入口（依 threat surface 分軸）</h2>
<p>四個子分類各自承擔一條 threat surface 的演示，避免單一 case 嘗試涵蓋所有威脅面。讀者依當下要分析的 threat 類型選分類入口：</p>
<table>
  <thead>
      <tr>
          <th>分類</th>
          <th>演示的 threat surface</th>
          <th>典型 chain anchor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/" data-link-title="7.R7.1 Identity &amp; Access 類案例" data-link-desc="整理身分流程、社交工程、支援系統與 token 鏈的事故案例">Identity &amp; Access</a></td>
          <td>身分、認證流程、社交工程、第三方身分鏈</td>
          <td>7.2 身分與授權邊界 + 7.5 federated trust</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/" data-link-title="7.R7.2 Supply Chain 類案例" data-link-desc="整理第三方整合、CI/CD、更新鏈、開源與 MSP 供應鏈事故案例">Supply Chain</a></td>
          <td>CI/CD、更新鏈、RMM、開源與 MSP 供應鏈</td>
          <td>7.6 供應鏈完整性與 artifact 信任</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/" data-link-title="7.R7.3 Edge Exposure 類案例" data-link-desc="整理邊界設備、外網入口、管理平面與鏈式漏洞事故案例">Edge Exposure</a></td>
          <td>邊界設備、外網入口、管理平面、鏈式漏洞</td>
          <td>7.3 入口與伺服端保護 + 7.12 偵測涵蓋</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/" data-link-title="7.R7.4 Data Exfiltration 類案例" data-link-desc="整理資料外送、備份風險、勒索回復與營運衝擊相關事故案例">Data Exfiltration</a></td>
          <td>資料外送、備份鏈、營運中斷與回復壓力</td>
          <td>7.9 資料保護 + 7.10 資料 residency + recovery readiness</td>
      </tr>
  </tbody>
</table>
<p>跨分類引用時、以同 threat surface 的 case 互相 link 為主、跨 surface 視為 multi-vector 案例（如 MGM 同時在 identity-access 與 ops-impact）、需在演示焦點段明示。</p>
<ul>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/case-reference-map/" data-link-title="7.R7.M 案例引用地圖（服務主題 -&gt; 案例 -&gt; workflow）" data-link-desc="把服務主題連到完整案例體系，再連回 incident workflow 檢查點">案例引用地圖</a>：服務主題到案例與 workflow 檢查點的對照。</li>
</ul>
<h2 id="使用方式">使用方式</h2>
<ol>
<li>先在服務章節定義風險主題與控制面。</li>
<li>選一篇同 threat surface 的 case、引用「如果 workflow 少一步會發生什麼」+「演示焦點」。</li>
<li>沿 case 的「從本案例到實作的 chain」走到對應 problem-card（若有）/ 主章節點 / blue-team scenario。</li>
<li>把該步驟落地到 <a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a> 的 runbook 流程。</li>
</ol>
]]></content:encoded></item><item><title>7.R7.1 Identity &amp; Access 類案例</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/</guid><description>&lt;p>本分類的責任是檢查身分與授權流程是否能在攻擊壓力下維持邊界。核心判讀是：登入成功只代表入口被通過，控制面仍需要持續驗證、隔離與收斂。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/" data-link-title="7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散" data-link-desc="從社交工程到內部工具存取，拆解身分流程與權限邊界的失效點">Uber 2022：MFA 疲勞與內部工具擴散&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &amp;#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023：支援流程與身分供應鏈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/" data-link-title="7.R7.1.3 Twilio 2022：社交工程與員工帳號路徑" data-link-desc="社交工程如何穿透員工身分流程，並影響下游客戶與供應鏈">Twilio 2022：社交工程與員工帳號路徑&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/" data-link-title="7.R7.1.4 MGM 2023：身分流程被打穿後的營運中斷" data-link-desc="社交工程造成身分邊界失守後，如何演變成可用性與營運衝擊">MGM 2023：身分流程被打穿後的營運中斷&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/" data-link-title="7.R7.1.5 Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取" data-link-desc="從簽章金鑰保護失效到雲端郵件存取，拆解身分信任鏈的關鍵控制點">Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023：供應商事件後的身分收斂&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/" data-link-title="7.R7.1.7 Slack 2022：企業 token 與程式碼資產路徑" data-link-desc="員工帳號被社交工程利用後，企業 token 與私有程式碼資產的防線如何運作">Slack 2022：企業 token 與程式碼資產路徑&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/dropbox-2022-code-repo-phishing-chain/" data-link-title="7.R7.1.8 Dropbox 2022：釣魚入侵與程式碼倉儲風險" data-link-desc="從員工釣魚事件到私有程式碼資產保護，建立身分與研發資產的聯防流程">Dropbox 2022：釣魚入侵與程式碼倉儲風險&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本分類的責任是檢查身分與授權流程是否能在攻擊壓力下維持邊界。核心判讀是：登入成功只代表入口被通過，控制面仍需要持續驗證、隔離與收斂。</p>
<h2 id="案例列表">案例列表</h2>
<ul>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/" data-link-title="7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散" data-link-desc="從社交工程到內部工具存取，拆解身分流程與權限邊界的失效點">Uber 2022：MFA 疲勞與內部工具擴散</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023：支援流程與身分供應鏈</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/" data-link-title="7.R7.1.3 Twilio 2022：社交工程與員工帳號路徑" data-link-desc="社交工程如何穿透員工身分流程，並影響下游客戶與供應鏈">Twilio 2022：社交工程與員工帳號路徑</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/" data-link-title="7.R7.1.4 MGM 2023：身分流程被打穿後的營運中斷" data-link-desc="社交工程造成身分邊界失守後，如何演變成可用性與營運衝擊">MGM 2023：身分流程被打穿後的營運中斷</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/" data-link-title="7.R7.1.5 Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取" data-link-desc="從簽章金鑰保護失效到雲端郵件存取，拆解身分信任鏈的關鍵控制點">Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023：供應商事件後的身分收斂</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/" data-link-title="7.R7.1.7 Slack 2022：企業 token 與程式碼資產路徑" data-link-desc="員工帳號被社交工程利用後，企業 token 與私有程式碼資產的防線如何運作">Slack 2022：企業 token 與程式碼資產路徑</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/dropbox-2022-code-repo-phishing-chain/" data-link-title="7.R7.1.8 Dropbox 2022：釣魚入侵與程式碼倉儲風險" data-link-desc="從員工釣魚事件到私有程式碼資產保護，建立身分與研發資產的聯防流程">Dropbox 2022：釣魚入侵與程式碼倉儲風險</a></li>
</ul>
]]></content:encoded></item><item><title>7.R7.2 Supply Chain 類案例</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/</guid><description>&lt;p>本分類的責任是驗證信任鏈在外部節點失效時是否可快速收斂。核心判讀是：只要系統信任外部交付或整合，workflow 就要先設計凍結、驗證、輪替與回復路由。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020：更新鏈被濫用&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022：第三方 token 供應鏈風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/" data-link-title="7.R7.2.3 CircleCI 2023：CI secrets 輪替壓力" data-link-desc="工程端點入侵後，CI 平台 secrets 如何成為高風險擴散點">CircleCI 2023：CI secrets 輪替壓力&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024：開源供應鏈長期滲透&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/" data-link-title="7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險" data-link-desc="CI 平台入口被利用後，如何沿著建置與發佈流程擴散供應鏈風險">TeamCity 2023：CI 入口漏洞與交付鏈風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/screenconnect-cve-2024-1709-rmm-entrypoint/" data-link-title="7.R7.2.6 ScreenConnect 2024：RMM 平台入口與下游擴散" data-link-desc="遠端管理平台入口被利用後，服務商與客戶環境會同步承壓">ScreenConnect 2024：RMM 平台入口與下游擴散&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell 2021：共用元件風險與修補鏈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023：桌面軟體更新鏈攻擊&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/kaseya-vsa-2021-msp-ransomware-chain/" data-link-title="7.R7.2.9 Kaseya VSA 2021：MSP 供應鏈擴散路徑" data-link-desc="管理平台事件透過 MSP 模型向多客戶擴散時，workflow 應如何分層應對">Kaseya VSA 2021：MSP 供應鏈擴散路徑&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/" data-link-title="7.R7.2.10 TeamCity 2024：CVE-2024-27198/27199 入口鏈" data-link-desc="TeamCity 連續漏洞揭示 CI 平台入口繞過與路徑穿越的供應鏈風險">TeamCity 2024：CVE-2024-27198/27199 入口鏈&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本分類的責任是驗證信任鏈在外部節點失效時是否可快速收斂。核心判讀是：只要系統信任外部交付或整合，workflow 就要先設計凍結、驗證、輪替與回復路由。</p>
<h2 id="案例列表">案例列表</h2>
<ul>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020：更新鏈被濫用</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022：第三方 token 供應鏈風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/" data-link-title="7.R7.2.3 CircleCI 2023：CI secrets 輪替壓力" data-link-desc="工程端點入侵後，CI 平台 secrets 如何成為高風險擴散點">CircleCI 2023：CI secrets 輪替壓力</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ Backdoor 2024：開源供應鏈長期滲透</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/" data-link-title="7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險" data-link-desc="CI 平台入口被利用後，如何沿著建置與發佈流程擴散供應鏈風險">TeamCity 2023：CI 入口漏洞與交付鏈風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/screenconnect-cve-2024-1709-rmm-entrypoint/" data-link-title="7.R7.2.6 ScreenConnect 2024：RMM 平台入口與下游擴散" data-link-desc="遠端管理平台入口被利用後，服務商與客戶環境會同步承壓">ScreenConnect 2024：RMM 平台入口與下游擴散</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell 2021：共用元件風險與修補鏈</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023：桌面軟體更新鏈攻擊</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/kaseya-vsa-2021-msp-ransomware-chain/" data-link-title="7.R7.2.9 Kaseya VSA 2021：MSP 供應鏈擴散路徑" data-link-desc="管理平台事件透過 MSP 模型向多客戶擴散時，workflow 應如何分層應對">Kaseya VSA 2021：MSP 供應鏈擴散路徑</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/" data-link-title="7.R7.2.10 TeamCity 2024：CVE-2024-27198/27199 入口鏈" data-link-desc="TeamCity 連續漏洞揭示 CI 平台入口繞過與路徑穿越的供應鏈風險">TeamCity 2024：CVE-2024-27198/27199 入口鏈</a></li>
</ul>
]]></content:encoded></item><item><title>7.R7.3 Edge Exposure 類案例</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/</guid><description>&lt;p>本分類的責任是把外網入口與邊界設備風險轉成可執行流程。核心判讀是：入口暴露、修補時差、攻擊自動化會同時放大事件規模，流程需要先隔離再修補再驗證。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/moveit-2023-mass-exfiltration/" data-link-title="7.R7.3.1 MOVEit 2023：外網檔案服務批量外送" data-link-desc="MFT 對外入口在零時差事件中如何被批量利用">MOVEit 2023：外網檔案服務批量外送&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/ivanti-2024-vpn-chain/" data-link-title="7.R7.3.2 Ivanti 2024：CVE-2023-46805/2024-21887 VPN 邊界漏洞鏈" data-link-desc="多漏洞串接下，邊界設備事件如何轉為持續控制風險">Ivanti 2024：VPN 邊界漏洞鏈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/" data-link-title="7.R7.3.3 Citrix Bleed 2023：會話被劫持與重放風險" data-link-desc="邊界設備會話資料外洩後，如何演變成帳號與服務風險">Citrix Bleed 2023：會話被劫持與重放風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/" data-link-title="7.R7.3.4 PAN-OS 2024：邊界設備遠端命令執行" data-link-desc="邊界設備 RCE 事件如何迫使團隊在修補與營運可用性間快速取捨">PAN-OS 2024：邊界設備遠端命令執行&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/papercut-cve-2023-27350-auth-bypass-rce/" data-link-title="7.R7.3.5 PaperCut 2023：認證繞過與入口執行風險" data-link-desc="管理平台入口若被認證繞過，內部列印與服務節點會暴露在遠端控制風險">PaperCut 2023：認證繞過與入口執行風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-cve-2022-26134-ognl-rce/" data-link-title="7.R7.3.6 Confluence 2022：網站入口 RCE 與知識系統風險" data-link-desc="協作平台外網入口被打穿時，內部知識與憑證線索會同步外露">Confluence 2022：網站入口 RCE 與知識系統風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/cisco-ios-xe-cve-2023-20198-webui-chain/" data-link-title="7.R7.3.7 Cisco IOS XE 2023：Web UI 管理面風險" data-link-desc="網通設備管理介面暴露時，攻擊可直接穿透邊界控制平面">Cisco IOS XE 2023：Web UI 管理面風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-ssl-vpn-cve-2024-21762/" data-link-title="7.R7.3.8 Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口" data-link-desc="VPN 邊界漏洞發生時，入口隔離與修補節奏需要同時啟動">Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/sysaid-cve-2023-47246-itsm-entrypoint/" data-link-title="7.R7.3.9 SysAid 2023：ITSM 入口與維運流程風險" data-link-desc="ITSM 服務入口被利用後，維運流程會成為擴散加速器">SysAid 2023：ITSM 入口與維運流程風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/juniper-cve-2023-36844-vpn-chain/" data-link-title="7.R7.3.10 Juniper 2023：網通設備鏈式漏洞窗口" data-link-desc="鏈式漏洞出現在核心網通設備時，修補與流量穩定性需要同步決策">Juniper 2023：網通設備鏈式漏洞窗口&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/servicenow-cve-2024-4879-enterprise-platform/" data-link-title="7.R7.3.11 ServiceNow 2024：企業平台入口風險" data-link-desc="企業核心平台漏洞出現時，服務流程與資料流程都需要同步收斂">ServiceNow 2024：企業平台入口風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/check-point-cve-2024-24919-vpn-info-disclosure/" data-link-title="7.R7.3.12 Check Point 2024：VPN 資訊外洩與會話風險" data-link-desc="邊界設備資訊外洩漏洞可快速轉為憑證與會話濫用風險">Check Point 2024：VPN 資訊外洩與會話風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxylogon-2021-exchange-entry-chain/" data-link-title="7.R7.3.13 ProxyLogon 2021：CVE-2021-26855/27065 入口鏈式失效" data-link-desc="郵件系統入口漏洞被串接利用時，事件會迅速擴大到內部服務邊界">ProxyLogon 2021：Exchange 入口鏈式失效&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxyshell-2021-exchange-post-auth-chain/" data-link-title="7.R7.3.14 ProxyShell 2021：CVE-2021-34473/34523/31207 後續鏈式攻擊" data-link-desc="同類入口平台在後續漏洞波次中，如何建立持續修補與驗證機制">ProxyShell 2021：Exchange 後續鏈式攻擊&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortios-cve-2022-42475-vpn-zero-day/" data-link-title="7.R7.3.15 FortiOS 2022：VPN 零時差事件節奏" data-link-desc="邊界設備零時差事件需要隔離、輪替、復測的完整鏈條">FortiOS 2022：VPN 零時差事件節奏&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-adc-2023-follow-on-session-risk/" data-link-title="7.R7.3.16 Citrix ADC 後續事件：Session 重放延伸" data-link-desc="同一波邊界事件在後續通報階段，重點轉為會話與憑證收斂">Citrix ADC 後續事件：Session 重放延伸&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-2023-cve-22515-22518-access-control-chain/" data-link-title="7.R7.3.17 Confluence 2023：CVE-2023-22515/22518 權限控制鏈" data-link-desc="Confluence 權限控制弱點在連續漏洞波次中如何擴大入口風險">Confluence 2023：CVE-2023-22515/22518 權限控制鏈&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-cve-2023-3519-code-injection/" data-link-title="7.R7.3.18 Citrix 2023：CVE-2023-3519 邊界代碼注入" data-link-desc="NetScaler 邊界入口代碼注入事件揭示管理平面快速失守風險">Citrix 2023：CVE-2023-3519 邊界代碼注入&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/f5-bigip-cve-2023-46747-auth-bypass/" data-link-title="7.R7.3.19 F5 BIG-IP 2023：CVE-2023-46747 認證繞過" data-link-desc="BIG-IP 組態管理入口認證繞過如何放大邊界設備治理壓力">F5 BIG-IP 2023：CVE-2023-46747 認證繞過&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2022-40684-auth-bypass/" data-link-title="7.R7.3.20 Fortinet 2022：CVE-2022-40684 認證繞過" data-link-desc="Fortinet 多產品認證繞過事件反映邊界與管理面共享風險">Fortinet 2022：CVE-2022-40684 認證繞過&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2023-27997-sslvpn-overflow/" data-link-title="7.R7.3.21 Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位" data-link-desc="SSL-VPN 漏洞在邊界設備上會放大大規模掃描與利用速度">Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/forticlient-ems-cve-2023-48788-sqli/" data-link-title="7.R7.3.22 FortiClient EMS 2023：CVE-2023-48788 SQL 注入" data-link-desc="端點管理平台 SQL 注入事件揭示管理平面資料與權限風險">FortiClient EMS 2023：CVE-2023-48788 SQL 注入&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/manageengine-adself-cve-2021-40539-auth-bypass/" data-link-title="7.R7.3.23 ManageEngine 2021：CVE-2021-40539 認證繞過" data-link-desc="身分服務入口認證繞過會把帳號管理流程直接暴露在攻擊鏈上">ManageEngine 2021：CVE-2021-40539 認證繞過&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/usaherds-cve-2021-44207-hardcoded-credential/" data-link-title="7.R7.3.24 USAHERDS 2021：CVE-2021-44207 硬編碼憑證" data-link-desc="硬編碼憑證事件展示供應商系統配置治理與存取控制的共同風險">USAHERDS 2021：CVE-2021-44207 硬編碼憑證&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本分類的責任是把外網入口與邊界設備風險轉成可執行流程。核心判讀是：入口暴露、修補時差、攻擊自動化會同時放大事件規模，流程需要先隔離再修補再驗證。</p>
<h2 id="案例列表">案例列表</h2>
<ul>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/moveit-2023-mass-exfiltration/" data-link-title="7.R7.3.1 MOVEit 2023：外網檔案服務批量外送" data-link-desc="MFT 對外入口在零時差事件中如何被批量利用">MOVEit 2023：外網檔案服務批量外送</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/ivanti-2024-vpn-chain/" data-link-title="7.R7.3.2 Ivanti 2024：CVE-2023-46805/2024-21887 VPN 邊界漏洞鏈" data-link-desc="多漏洞串接下，邊界設備事件如何轉為持續控制風險">Ivanti 2024：VPN 邊界漏洞鏈</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/" data-link-title="7.R7.3.3 Citrix Bleed 2023：會話被劫持與重放風險" data-link-desc="邊界設備會話資料外洩後，如何演變成帳號與服務風險">Citrix Bleed 2023：會話被劫持與重放風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/" data-link-title="7.R7.3.4 PAN-OS 2024：邊界設備遠端命令執行" data-link-desc="邊界設備 RCE 事件如何迫使團隊在修補與營運可用性間快速取捨">PAN-OS 2024：邊界設備遠端命令執行</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/papercut-cve-2023-27350-auth-bypass-rce/" data-link-title="7.R7.3.5 PaperCut 2023：認證繞過與入口執行風險" data-link-desc="管理平台入口若被認證繞過，內部列印與服務節點會暴露在遠端控制風險">PaperCut 2023：認證繞過與入口執行風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-cve-2022-26134-ognl-rce/" data-link-title="7.R7.3.6 Confluence 2022：網站入口 RCE 與知識系統風險" data-link-desc="協作平台外網入口被打穿時，內部知識與憑證線索會同步外露">Confluence 2022：網站入口 RCE 與知識系統風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/cisco-ios-xe-cve-2023-20198-webui-chain/" data-link-title="7.R7.3.7 Cisco IOS XE 2023：Web UI 管理面風險" data-link-desc="網通設備管理介面暴露時，攻擊可直接穿透邊界控制平面">Cisco IOS XE 2023：Web UI 管理面風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-ssl-vpn-cve-2024-21762/" data-link-title="7.R7.3.8 Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口" data-link-desc="VPN 邊界漏洞發生時，入口隔離與修補節奏需要同時啟動">Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/sysaid-cve-2023-47246-itsm-entrypoint/" data-link-title="7.R7.3.9 SysAid 2023：ITSM 入口與維運流程風險" data-link-desc="ITSM 服務入口被利用後，維運流程會成為擴散加速器">SysAid 2023：ITSM 入口與維運流程風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/juniper-cve-2023-36844-vpn-chain/" data-link-title="7.R7.3.10 Juniper 2023：網通設備鏈式漏洞窗口" data-link-desc="鏈式漏洞出現在核心網通設備時，修補與流量穩定性需要同步決策">Juniper 2023：網通設備鏈式漏洞窗口</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/servicenow-cve-2024-4879-enterprise-platform/" data-link-title="7.R7.3.11 ServiceNow 2024：企業平台入口風險" data-link-desc="企業核心平台漏洞出現時，服務流程與資料流程都需要同步收斂">ServiceNow 2024：企業平台入口風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/check-point-cve-2024-24919-vpn-info-disclosure/" data-link-title="7.R7.3.12 Check Point 2024：VPN 資訊外洩與會話風險" data-link-desc="邊界設備資訊外洩漏洞可快速轉為憑證與會話濫用風險">Check Point 2024：VPN 資訊外洩與會話風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxylogon-2021-exchange-entry-chain/" data-link-title="7.R7.3.13 ProxyLogon 2021：CVE-2021-26855/27065 入口鏈式失效" data-link-desc="郵件系統入口漏洞被串接利用時，事件會迅速擴大到內部服務邊界">ProxyLogon 2021：Exchange 入口鏈式失效</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxyshell-2021-exchange-post-auth-chain/" data-link-title="7.R7.3.14 ProxyShell 2021：CVE-2021-34473/34523/31207 後續鏈式攻擊" data-link-desc="同類入口平台在後續漏洞波次中，如何建立持續修補與驗證機制">ProxyShell 2021：Exchange 後續鏈式攻擊</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortios-cve-2022-42475-vpn-zero-day/" data-link-title="7.R7.3.15 FortiOS 2022：VPN 零時差事件節奏" data-link-desc="邊界設備零時差事件需要隔離、輪替、復測的完整鏈條">FortiOS 2022：VPN 零時差事件節奏</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-adc-2023-follow-on-session-risk/" data-link-title="7.R7.3.16 Citrix ADC 後續事件：Session 重放延伸" data-link-desc="同一波邊界事件在後續通報階段，重點轉為會話與憑證收斂">Citrix ADC 後續事件：Session 重放延伸</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-2023-cve-22515-22518-access-control-chain/" data-link-title="7.R7.3.17 Confluence 2023：CVE-2023-22515/22518 權限控制鏈" data-link-desc="Confluence 權限控制弱點在連續漏洞波次中如何擴大入口風險">Confluence 2023：CVE-2023-22515/22518 權限控制鏈</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-cve-2023-3519-code-injection/" data-link-title="7.R7.3.18 Citrix 2023：CVE-2023-3519 邊界代碼注入" data-link-desc="NetScaler 邊界入口代碼注入事件揭示管理平面快速失守風險">Citrix 2023：CVE-2023-3519 邊界代碼注入</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/f5-bigip-cve-2023-46747-auth-bypass/" data-link-title="7.R7.3.19 F5 BIG-IP 2023：CVE-2023-46747 認證繞過" data-link-desc="BIG-IP 組態管理入口認證繞過如何放大邊界設備治理壓力">F5 BIG-IP 2023：CVE-2023-46747 認證繞過</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2022-40684-auth-bypass/" data-link-title="7.R7.3.20 Fortinet 2022：CVE-2022-40684 認證繞過" data-link-desc="Fortinet 多產品認證繞過事件反映邊界與管理面共享風險">Fortinet 2022：CVE-2022-40684 認證繞過</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2023-27997-sslvpn-overflow/" data-link-title="7.R7.3.21 Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位" data-link-desc="SSL-VPN 漏洞在邊界設備上會放大大規模掃描與利用速度">Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/forticlient-ems-cve-2023-48788-sqli/" data-link-title="7.R7.3.22 FortiClient EMS 2023：CVE-2023-48788 SQL 注入" data-link-desc="端點管理平台 SQL 注入事件揭示管理平面資料與權限風險">FortiClient EMS 2023：CVE-2023-48788 SQL 注入</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/manageengine-adself-cve-2021-40539-auth-bypass/" data-link-title="7.R7.3.23 ManageEngine 2021：CVE-2021-40539 認證繞過" data-link-desc="身分服務入口認證繞過會把帳號管理流程直接暴露在攻擊鏈上">ManageEngine 2021：CVE-2021-40539 認證繞過</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/usaherds-cve-2021-44207-hardcoded-credential/" data-link-title="7.R7.3.24 USAHERDS 2021：CVE-2021-44207 硬編碼憑證" data-link-desc="硬編碼憑證事件展示供應商系統配置治理與存取控制的共同風險">USAHERDS 2021：CVE-2021-44207 硬編碼憑證</a></li>
</ul>
]]></content:encoded></item><item><title>7.R7.4 Data Exfiltration 類案例</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/</guid><description>&lt;p>本分類的責任是把資料外送與營運中斷風險轉成可驗證的治理步驟。核心判讀是：資料治理、回復順序與跨團隊通報要同步設計，才能縮小外送規模與停擺時間。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/" data-link-title="7.R7.4.1 LastPass 2022：備份路徑與鏈式入侵" data-link-desc="開發環境資訊外流如何沿著備份路徑擴大成資料風險">LastPass 2022：備份路徑與鏈式入侵&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024：憑證濫用與資料竊取&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/" data-link-title="7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷" data-link-desc="醫療支付中樞事件如何同時衝擊資料安全與業務連續性">Change Healthcare 2024：資料事件轉為營運中斷&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/mailchimp-2023-support-tool-abuse/" data-link-title="7.R7.4.4 Mailchimp 2023：支援工具路徑與客戶資料風險" data-link-desc="社交工程進入客服工具後，如何形成特定客戶資料存取風險">Mailchimp 2023：支援工具路徑與客戶資料風險&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/" data-link-title="7.R7.4.5 VMware ESXiArgs 2023：虛擬化平台勒索回復壓力" data-link-desc="虛擬化平台漏洞被利用後，回復策略與營運連續性會面臨同步壓力">VMware ESXiArgs 2023：虛擬化平台勒索回復壓力&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/" data-link-title="7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送" data-link-desc="對外檔案服務漏洞在企業環境常直接轉為資料外送風險">Progress WS_FTP 2023：檔案服務入口與資料外送&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/goanywhere-mft-2023-exfiltration-chain/" data-link-title="7.R7.4.7 GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈" data-link-desc="MFT 中樞服務漏洞會把檔案交換流程直接轉成資料外送風險">GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>本分類的責任是把資料外送與營運中斷風險轉成可驗證的治理步驟。核心判讀是：資料治理、回復順序與跨團隊通報要同步設計，才能縮小外送規模與停擺時間。</p>
<h2 id="案例列表">案例列表</h2>
<ul>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/" data-link-title="7.R7.4.1 LastPass 2022：備份路徑與鏈式入侵" data-link-desc="開發環境資訊外流如何沿著備份路徑擴大成資料風險">LastPass 2022：備份路徑與鏈式入侵</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024：憑證濫用與資料竊取</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/" data-link-title="7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷" data-link-desc="醫療支付中樞事件如何同時衝擊資料安全與業務連續性">Change Healthcare 2024：資料事件轉為營運中斷</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/mailchimp-2023-support-tool-abuse/" data-link-title="7.R7.4.4 Mailchimp 2023：支援工具路徑與客戶資料風險" data-link-desc="社交工程進入客服工具後，如何形成特定客戶資料存取風險">Mailchimp 2023：支援工具路徑與客戶資料風險</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/" data-link-title="7.R7.4.5 VMware ESXiArgs 2023：虛擬化平台勒索回復壓力" data-link-desc="虛擬化平台漏洞被利用後，回復策略與營運連續性會面臨同步壓力">VMware ESXiArgs 2023：虛擬化平台勒索回復壓力</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/" data-link-title="7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送" data-link-desc="對外檔案服務漏洞在企業環境常直接轉為資料外送風險">Progress WS_FTP 2023：檔案服務入口與資料外送</a></li>
<li><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/goanywhere-mft-2023-exfiltration-chain/" data-link-title="7.R7.4.7 GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈" data-link-desc="MFT 中樞服務漏洞會把檔案交換流程直接轉成資料外送風險">GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈</a></li>
</ul>
]]></content:encoded></item><item><title>7.R7.M 案例引用地圖（服務主題 -> 案例 -> workflow）</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/case-reference-map/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/case-reference-map/</guid><description>&lt;p>這份地圖的責任是提供雙向引用路由：服務設計可以從主題找到案例，incident workflow 可以從流程步驟回查案例證據。&lt;/p>
&lt;h2 id="認證與權限邊界">認證與權限邊界&lt;/h2>
&lt;p>這個主題處理身分入口、憑證信任鏈與高權限操作隔離。優先案例是 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/" data-link-title="7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散" data-link-desc="從社交工程到內部工具存取，拆解身分流程與權限邊界的失效點">Uber 2022&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/" data-link-title="7.R7.1.3 Twilio 2022：社交工程與員工帳號路徑" data-link-desc="社交工程如何穿透員工身分流程，並影響下游客戶與供應鏈">Twilio 2022&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/" data-link-title="7.R7.1.4 MGM 2023：身分流程被打穿後的營運中斷" data-link-desc="社交工程造成身分邊界失守後，如何演變成可用性與營運衝擊">MGM 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/" data-link-title="7.R7.1.5 Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取" data-link-desc="從簽章金鑰保護失效到雲端郵件存取，拆解身分信任鏈的關鍵控制點">Storm-0558 2023&lt;/a>。&lt;/p>
&lt;p>workflow 檢查點：高風險操作 step-up、異常身分即時隔離、跨租戶權杖異常升級。對應流程章節：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow&lt;/a>。&lt;/p>
&lt;h2 id="第三方整合與-token">第三方整合與 token&lt;/h2>
&lt;p>這個主題處理供應商事件傳導與 token 收斂速度。優先案例是 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &amp;#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/" data-link-title="7.R7.1.7 Slack 2022：企業 token 與程式碼資產路徑" data-link-desc="員工帳號被社交工程利用後，企業 token 與私有程式碼資產的防線如何運作">Slack 2022&lt;/a>。&lt;/p>
&lt;p>workflow 檢查點：第三方事件觸發全域 token 盤點、分域撤銷與輪替、供應商事件 playbook。對應流程章節：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow&lt;/a>。&lt;/p>
&lt;h2 id="cicd-與更新供應鏈">CI/CD 與更新供應鏈&lt;/h2>
&lt;p>這個主題處理 build 與更新信任鏈在事件中的凍結與恢復節奏。優先案例是 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/" data-link-title="7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險" data-link-desc="CI 平台入口被利用後，如何沿著建置與發佈流程擴散供應鏈風險">TeamCity 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/" data-link-title="7.R7.2.10 TeamCity 2024：CVE-2024-27198/27199 入口鏈" data-link-desc="TeamCity 連續漏洞揭示 CI 平台入口繞過與路徑穿越的供應鏈風險">TeamCity 2024&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/" data-link-title="7.R7.2.3 CircleCI 2023：CI secrets 輪替壓力" data-link-desc="工程端點入侵後，CI 平台 secrets 如何成為高風險擴散點">CircleCI 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ 2024&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell 2021&lt;/a>。&lt;/p>
&lt;p>workflow 檢查點：部署凍結、artifact 驗證、分批輪替 secrets、版本回退與復測。對應流程章節：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow&lt;/a>。&lt;/p>
&lt;h2 id="workload-identity-與聯邦信任">Workload identity 與聯邦信任&lt;/h2>
&lt;p>這個主題處理非人類身份、跨平台 token 交換與短時憑證收斂。優先案例是 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &amp;#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022&lt;/a>。&lt;/p>
&lt;p>workflow 檢查點：workload 身份來源回查、federation trust 重評估、短時憑證撤銷、跨平台 token scope 收斂。對應流程章節：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這份地圖的責任是提供雙向引用路由：服務設計可以從主題找到案例，incident workflow 可以從流程步驟回查案例證據。</p>
<h2 id="認證與權限邊界">認證與權限邊界</h2>
<p>這個主題處理身分入口、憑證信任鏈與高權限操作隔離。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/" data-link-title="7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散" data-link-desc="從社交工程到內部工具存取，拆解身分流程與權限邊界的失效點">Uber 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/" data-link-title="7.R7.1.3 Twilio 2022：社交工程與員工帳號路徑" data-link-desc="社交工程如何穿透員工身分流程，並影響下游客戶與供應鏈">Twilio 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/" data-link-title="7.R7.1.4 MGM 2023：身分流程被打穿後的營運中斷" data-link-desc="社交工程造成身分邊界失守後，如何演變成可用性與營運衝擊">MGM 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/" data-link-title="7.R7.1.5 Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取" data-link-desc="從簽章金鑰保護失效到雲端郵件存取，拆解身分信任鏈的關鍵控制點">Storm-0558 2023</a>。</p>
<p>workflow 檢查點：高風險操作 step-up、異常身分即時隔離、跨租戶權杖異常升級。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="第三方整合與-token">第三方整合與 token</h2>
<p>這個主題處理供應商事件傳導與 token 收斂速度。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/" data-link-title="7.R7.1.7 Slack 2022：企業 token 與程式碼資產路徑" data-link-desc="員工帳號被社交工程利用後，企業 token 與私有程式碼資產的防線如何運作">Slack 2022</a>。</p>
<p>workflow 檢查點：第三方事件觸發全域 token 盤點、分域撤銷與輪替、供應商事件 playbook。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="cicd-與更新供應鏈">CI/CD 與更新供應鏈</h2>
<p>這個主題處理 build 與更新信任鏈在事件中的凍結與恢復節奏。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/" data-link-title="7.R7.2.1 SolarWinds 2020：更新鏈被濫用" data-link-desc="合法更新流程遭植入後，攻擊者如何長期潛伏與橫向擴散">SolarWinds 2020</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/" data-link-title="7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險" data-link-desc="CI 平台入口被利用後，如何沿著建置與發佈流程擴散供應鏈風險">TeamCity 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/" data-link-title="7.R7.2.10 TeamCity 2024：CVE-2024-27198/27199 入口鏈" data-link-desc="TeamCity 連續漏洞揭示 CI 平台入口繞過與路徑穿越的供應鏈風險">TeamCity 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/" data-link-title="7.R7.2.3 CircleCI 2023：CI secrets 輪替壓力" data-link-desc="工程端點入侵後，CI 平台 secrets 如何成為高風險擴散點">CircleCI 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/" data-link-title="7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊" data-link-desc="合法更新流程被植入後，桌面端供應鏈事件如何傳到企業端點">3CX 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/" data-link-title="7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈" data-link-desc="共用元件漏洞如何同步影響多服務，並迫使團隊建立依賴治理 workflow">Log4Shell 2021</a>。</p>
<p>workflow 檢查點：部署凍結、artifact 驗證、分批輪替 secrets、版本回退與復測。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="workload-identity-與聯邦信任">Workload identity 與聯邦信任</h2>
<p>這個主題處理非人類身份、跨平台 token 交換與短時憑證收斂。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/" data-link-title="7.R7.1.2 Okta &#43; Cloudflare 2023：支援流程與身分供應鏈" data-link-desc="支援工單與第三方身份供應商路徑如何變成入侵鏈的一部分">Okta + Cloudflare 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/" data-link-title="7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂" data-link-desc="同一條供應商事件鏈，如何在客戶端變成 session 與 token 的收斂壓力">Cloudflare 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/" data-link-title="7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險" data-link-desc="第三方整合 token 被竊後，如何形成跨組織存取風險">GitHub OAuth 2022</a>。</p>
<p>workflow 檢查點：workload 身份來源回查、federation trust 重評估、短時憑證撤銷、跨平台 token scope 收斂。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="邊界設備與外網入口">邊界設備與外網入口</h2>
<p>這個主題處理暴露面高與修補窗口短的組合風險。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/moveit-2023-mass-exfiltration/" data-link-title="7.R7.3.1 MOVEit 2023：外網檔案服務批量外送" data-link-desc="MFT 對外入口在零時差事件中如何被批量利用">MOVEit 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/ivanti-2024-vpn-chain/" data-link-title="7.R7.3.2 Ivanti 2024：CVE-2023-46805/2024-21887 VPN 邊界漏洞鏈" data-link-desc="多漏洞串接下，邊界設備事件如何轉為持續控制風險">Ivanti 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/" data-link-title="7.R7.3.4 PAN-OS 2024：邊界設備遠端命令執行" data-link-desc="邊界設備 RCE 事件如何迫使團隊在修補與營運可用性間快速取捨">PAN-OS 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-ssl-vpn-cve-2024-21762/" data-link-title="7.R7.3.8 Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口" data-link-desc="VPN 邊界漏洞發生時，入口隔離與修補節奏需要同時啟動">Fortinet SSL-VPN 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2023-27997-sslvpn-overflow/" data-link-title="7.R7.3.21 Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位" data-link-desc="SSL-VPN 漏洞在邊界設備上會放大大規模掃描與利用速度">Fortinet 2023（27997）</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-cve-2023-3519-code-injection/" data-link-title="7.R7.3.18 Citrix 2023：CVE-2023-3519 邊界代碼注入" data-link-desc="NetScaler 邊界入口代碼注入事件揭示管理平面快速失守風險">Citrix 2023（3519）</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/f5-bigip-cve-2023-46747-auth-bypass/" data-link-title="7.R7.3.19 F5 BIG-IP 2023：CVE-2023-46747 認證繞過" data-link-desc="BIG-IP 組態管理入口認證繞過如何放大邊界設備治理壓力">F5 BIG-IP 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxylogon-2021-exchange-entry-chain/" data-link-title="7.R7.3.13 ProxyLogon 2021：CVE-2021-26855/27065 入口鏈式失效" data-link-desc="郵件系統入口漏洞被串接利用時，事件會迅速擴大到內部服務邊界">ProxyLogon 2021</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxyshell-2021-exchange-post-auth-chain/" data-link-title="7.R7.3.14 ProxyShell 2021：CVE-2021-34473/34523/31207 後續鏈式攻擊" data-link-desc="同類入口平台在後續漏洞波次中，如何建立持續修補與驗證機制">ProxyShell 2021</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-adc-2023-follow-on-session-risk/" data-link-title="7.R7.3.16 Citrix ADC 後續事件：Session 重放延伸" data-link-desc="同一波邊界事件在後續通報階段，重點轉為會話與憑證收斂">Citrix 後續事件</a>。</p>
<p>workflow 檢查點：漏洞公告即隔離、分區修補、修補後狀態驗證、session 或憑證全域收斂。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="資料外送與營運回復">資料外送與營運回復</h2>
<p>這個主題處理資料外送與營運停擺同步發生時的決策順序。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/" data-link-title="7.R7.4.1 LastPass 2022：備份路徑與鏈式入侵" data-link-desc="開發環境資訊外流如何沿著備份路徑擴大成資料風險">LastPass 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/" data-link-title="7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送" data-link-desc="對外檔案服務漏洞在企業環境常直接轉為資料外送風險">WS_FTP 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/goanywhere-mft-2023-exfiltration-chain/" data-link-title="7.R7.4.7 GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈" data-link-desc="MFT 中樞服務漏洞會把檔案交換流程直接轉成資料外送風險">GoAnywhere 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/" data-link-title="7.R7.4.5 VMware ESXiArgs 2023：虛擬化平台勒索回復壓力" data-link-desc="虛擬化平台漏洞被利用後，回復策略與營運連續性會面臨同步壓力">VMware ESXiArgs 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/" data-link-title="7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷" data-link-desc="醫療支付中樞事件如何同時衝擊資料安全與業務連續性">Change Healthcare 2024</a>。</p>
<p>workflow 檢查點：外送封鎖、受影響清單盤點、RTO/RPO 路由、回復優先級排序與跨組織通報。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="資料駐留刪除與證據鏈">資料駐留、刪除與證據鏈</h2>
<p>這個主題處理資料位置、刪除閉環、備份長尾與可驗證證據。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/" data-link-title="7.R7.4.1 LastPass 2022：備份路徑與鏈式入侵" data-link-desc="開發環境資訊外流如何沿著備份路徑擴大成資料風險">LastPass 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/" data-link-title="7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送" data-link-desc="對外檔案服務漏洞在企業環境常直接轉為資料外送風險">WS_FTP 2023</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/" data-link-title="7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷" data-link-desc="醫療支付中樞事件如何同時衝擊資料安全與業務連續性">Change Healthcare 2024</a>。</p>
<p>workflow 檢查點：資料位置清單、衍生資料刪除驗證、備份保留例外、刪除證據與通報證據對齊。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="偵測治理與例外-tripwire">偵測治理與例外 tripwire</h2>
<p>這個主題處理偵測覆蓋、訊號關聯、例外期限與重新評估觸發器。優先案例是 <a href="/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/" data-link-title="7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散" data-link-desc="從社交工程到內部工具存取，拆解身分流程與權限邊界的失效點">Uber 2022</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/" data-link-title="7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取" data-link-desc="外洩憑證與 MFA 缺口如何在資料平台形成高風險外送事件">Snowflake 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/" data-link-title="7.R7.3.4 PAN-OS 2024：邊界設備遠端命令執行" data-link-desc="邊界設備 RCE 事件如何迫使團隊在修補與營運可用性間快速取捨">PAN-OS 2024</a>、<a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/" data-link-title="7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透" data-link-desc="開源維護鏈遭滲透後，為何會直接影響廣泛 Linux 發行流程">XZ 2024</a>。</p>
<p>workflow 檢查點：高風險訊號關聯、例外到期重審、重大事件 tripwire、復盤後偵測覆蓋率修正。對應流程章節：<a href="/blog/backend/08-incident-response/incident-report-to-workflow/" data-link-title="8.8 事故報告轉 workflow：從案例到日常流程" data-link-desc="把事故報告拆成可執行流程，並與 red-team 案例庫建立雙向引用">incident-report-to-workflow</a>。</p>
<h2 id="使用規則">使用規則</h2>
<ol>
<li>每個服務主題至少引用一篇同類型案例。</li>
<li>每次引用至少帶出一個可操作 workflow 檢查點。</li>
<li>每個 runbook 變更都回寫到對應案例與 workflow 章節，維持雙向可追溯。</li>
</ol>
]]></content:encoded></item><item><title>7.R7.1.1 Uber 2022：MFA 疲勞與內部工具擴散</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/uber-2022-mfa-fatigue/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2022 年 9 月，攻擊者先取得承包商帳號，再透過重複 MFA 請求與社交工程進入內部系統，後續接觸到多個內部管理工具。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：social engineering → MFA push fatigue → 既有身份接入內部高權限工具的 identity-chain 失效。供應鏈植入、邊界零時差、資料外送量級壓力等其他 threat surface 由 supply-chain / edge-exposure / data-exfiltration 案例分類承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>取得初始帳號。&lt;/li>
&lt;li>以 MFA fatigue 增加使用者誤同意機率。&lt;/li>
&lt;li>使用已登入身份接觸內部高權限工具。&lt;/li>
&lt;li>擴大可見範圍並造成營運干擾。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>高風險登入路徑缺少 step-up 驗證。&lt;/li>
&lt;li>內部工具授權邊界不足，初始落點可快速擴散。&lt;/li>
&lt;li>身分異常事件與值班告警串接不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若值班流程缺少「異常 MFA 請求密度」檢查，團隊會把登入異常當成一般使用者問題，導致處置時間延後、擴散面增加。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：高風險操作要求 phishing-resistant 強認證（WebAuthn / passkey、阻擋可被連續同意的 push approval）+ 裝置信任綁定（managed device / posture check），mechanism 是讓「同意」不再是攻擊者唯一所需的人類動作。&lt;/li>
&lt;li>日常：監控 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication&lt;/a> 異常事件（短時內 MFA 請求密度、跨地理 / 跨裝置序列）與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 升級規則。&lt;/li>
&lt;li>事故中：快速凍結可疑身分、切斷高權限工具存取（依賴內部工具事先有 token revocation 與 session kill 能力）、建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用&lt;/a> —— 把本案例的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> —— 把樣式轉成 tabletop 與 release gate 欄位。&lt;/li>
&lt;/ul>
&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;a href="https://www.uber.com/newsroom/security-update/">uber.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊者進入路徑、影響範圍與第一手時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>Scattered Spider / UNC3944 TTP、跨組織 social engineering&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-sms-phishing-sim-swapping-ransomware/">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>Mandiant 對 social engineering、SIM swap、後續勒索鏈 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2022 年 9 月，攻擊者先取得承包商帳號，再透過重複 MFA 請求與社交工程進入內部系統，後續接觸到多個內部管理工具。</p>
<p><strong>本案例的演示焦點</strong>：social engineering → MFA push fatigue → 既有身份接入內部高權限工具的 identity-chain 失效。供應鏈植入、邊界零時差、資料外送量級壓力等其他 threat surface 由 supply-chain / edge-exposure / data-exfiltration 案例分類承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>取得初始帳號。</li>
<li>以 MFA fatigue 增加使用者誤同意機率。</li>
<li>使用已登入身份接觸內部高權限工具。</li>
<li>擴大可見範圍並造成營運干擾。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>高風險登入路徑缺少 step-up 驗證。</li>
<li>內部工具授權邊界不足，初始落點可快速擴散。</li>
<li>身分異常事件與值班告警串接不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若值班流程缺少「異常 MFA 請求密度」檢查，團隊會把登入異常當成一般使用者問題，導致處置時間延後、擴散面增加。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：高風險操作要求 phishing-resistant 強認證（WebAuthn / passkey、阻擋可被連續同意的 push approval）+ 裝置信任綁定（managed device / posture check），mechanism 是讓「同意」不再是攻擊者唯一所需的人類動作。</li>
<li>日常：監控 <a href="/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication</a> 異常事件（短時內 MFA 請求密度、跨地理 / 跨裝置序列）與 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 升級規則。</li>
<li>事故中：快速凍結可疑身分、切斷高權限工具存取（依賴內部工具事先有 token revocation 與 session kill 能力）、建立 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a>。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用</a> —— 把本案例的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> —— 把樣式轉成 tabletop 與 release gate 欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.uber.com/newsroom/security-update/">uber.com</a></td>
          <td>官方</td>
          <td>攻擊者進入路徑、影響範圍與第一手時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>Scattered Spider / UNC3944 TTP、跨組織 social engineering</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-sms-phishing-sim-swapping-ransomware/">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>Mandiant 對 social engineering、SIM swap、後續勒索鏈 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.2 Okta + Cloudflare 2023：支援流程與身分供應鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/okta-cloudflare-2023-support-supply-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 10 到 11 月，Okta 與 Cloudflare 的公開說明都指出，攻擊者透過支援相關流程取得可用資訊，形成跨組織的身分供應鏈風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：上游供應商支援流程（HAR 檔 / 工單附件 / session token）→ 客戶側身分接管的跨組織 chain。重點在 support workflow 承載身分敏感材料時的邊界 / 通報節奏設計。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定支援流程與可取得的工單資料。&lt;/li>
&lt;li>利用流程缺口取得敏感資訊或權限線索。&lt;/li>
&lt;li>以第三方身份供應商作為橋接點延伸到客戶側。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>支援資料流沒有被視為高敏感資產。&lt;/li>
&lt;li>憑證或會話資料生命周期管理不足。&lt;/li>
&lt;li>供應商事件到客戶內部輪替流程沒有強制觸發。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「供應商事件觸發的全域憑證輪替」，事件會停在公告層，實際可利用的憑證仍留在環境中。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：支援系統資料分級、限制下載與外流路徑（HAR sanitizer、附件 retention 限制），mechanism 是讓支援系統的「便利性」不直接傳導到身分風險。&lt;/li>
&lt;li>日常：建立第三方事件觸發的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a>（含 cross-vendor coordination、客戶先發現的反向通報）。&lt;/li>
&lt;li>事故中：啟用供應商事件專用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">playbook&lt;/a>、執行輪替、追蹤、封鎖（前提是輪替能力涵蓋第三方授權 token、不只內部 session）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/fp-overscoped-third-party-token-grant/" data-link-title="7.R11.P12 第三方 token 授權範圍過寬" data-link-desc="說明第三方 token 授權範圍過寬如何放大供應商事件傳導">Overscoped 第三方 token grant&lt;/a> —— 把 support workflow 承載身分材料的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern&lt;/a> —— 把樣式轉成 tabletop、release gate 欄位與跨組織 owner 分工。&lt;/li>
&lt;/ul>
&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;a href="https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-root-cause">sec.okta.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊路徑、support system root cause、影響範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://blog.cloudflare.com/thanksgiving-2023-security-incident/">blog.cloudflare.com&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>客戶側偵測 / 即時回應、Zero Trust 防守效果（peer evidence）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC3944 對 SaaS / 身分供應鏈的攻擊模式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 10 到 11 月，Okta 與 Cloudflare 的公開說明都指出，攻擊者透過支援相關流程取得可用資訊，形成跨組織的身分供應鏈風險。</p>
<p><strong>本案例的演示焦點</strong>：上游供應商支援流程（HAR 檔 / 工單附件 / session token）→ 客戶側身分接管的跨組織 chain。重點在 support workflow 承載身分敏感材料時的邊界 / 通報節奏設計。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定支援流程與可取得的工單資料。</li>
<li>利用流程缺口取得敏感資訊或權限線索。</li>
<li>以第三方身份供應商作為橋接點延伸到客戶側。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>支援資料流沒有被視為高敏感資產。</li>
<li>憑證或會話資料生命周期管理不足。</li>
<li>供應商事件到客戶內部輪替流程沒有強制觸發。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「供應商事件觸發的全域憑證輪替」，事件會停在公告層，實際可利用的憑證仍留在環境中。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：支援系統資料分級、限制下載與外流路徑（HAR sanitizer、附件 retention 限制），mechanism 是讓支援系統的「便利性」不直接傳導到身分風險。</li>
<li>日常：建立第三方事件觸發的 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a>（含 cross-vendor coordination、客戶先發現的反向通報）。</li>
<li>事故中：啟用供應商事件專用 <a href="/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">playbook</a>、執行輪替、追蹤、封鎖（前提是輪替能力涵蓋第三方授權 token、不只內部 session）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/fp-overscoped-third-party-token-grant/" data-link-title="7.R11.P12 第三方 token 授權範圍過寬" data-link-desc="說明第三方 token 授權範圍過寬如何放大供應商事件傳導">Overscoped 第三方 token grant</a> —— 把 support workflow 承載身分材料的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern</a> —— 把樣式轉成 tabletop、release gate 欄位與跨組織 owner 分工。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-root-cause">sec.okta.com</a></td>
          <td>官方</td>
          <td>攻擊路徑、support system root cause、影響範圍</td>
      </tr>
      <tr>
          <td><a href="https://blog.cloudflare.com/thanksgiving-2023-security-incident/">blog.cloudflare.com</a></td>
          <td>政府/監管</td>
          <td>客戶側偵測 / 即時回應、Zero Trust 防守效果（peer evidence）</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC3944 對 SaaS / 身分供應鏈的攻擊模式</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.3 Twilio 2022：社交工程與員工帳號路徑</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/twilio-2022-social-engineering/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2022 年 8 月，Twilio 公告社交工程攻擊造成員工帳號被濫用，影響內部系統與部分客戶關聯風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：員工 phishing → 內部管理工具接管 → 下游客戶 / 供應鏈傳導的 identity-chain 風險。重點在「員工身份」即「客戶風險面」的傳導邊界。其他 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>以釣魚或社交工程瞄準員工。&lt;/li>
&lt;li>取得可登入的員工身份。&lt;/li>
&lt;li>使用合法身份移動到高價值系統與資料。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>員工身份保護流程對社交工程韌性不足。&lt;/li>
&lt;li>登入後的高敏感操作缺少額外驗證。&lt;/li>
&lt;li>身分異常事件與快速隔離機制不夠緊密。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「員工帳號異常即時隔離」步驟，攻擊者會持續用合法會話做橫向移動，調查難度與影響面同步上升。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：高風險管理操作要求二次核准（multi-party approval、不只 MFA），mechanism 是讓單一帳號接管無法觸發影響客戶的決策。&lt;/li>
&lt;li>日常：針對員工身份建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook&lt;/a>（管理工具登入跨地理 / 跨裝置 / 異常時段）。&lt;/li>
&lt;li>事故中：執行分批憑證輪替與權限縮減、控制 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>（前提是 token / 權限有 audit trail 可分批 scope）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/approval-flow-abuse/" data-link-title="7.R11.2 審核流程濫用" data-link-desc="說明審核節點為何會變成形式審核，進而放大高風險操作">核准流程濫用&lt;/a> —— 把員工身分 → 管理工具 → 客戶傳導的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> —— 把樣式轉成 tabletop 與 release gate 欄位。&lt;/li>
&lt;/ul>
&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;a href="https://www.twilio.com/en-us/blog/august-2022-social-engineering-attack">twilio.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊入口、影響範圍、員工 phishing kit 第一手 telemetry&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>Scattered Spider / UNC3944 跨組織 TTP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-sms-phishing-sim-swapping-ransomware/">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>Mandiant 對 SMS phishing / SIM swap 後續鏈 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2022 年 8 月，Twilio 公告社交工程攻擊造成員工帳號被濫用，影響內部系統與部分客戶關聯風險。</p>
<p><strong>本案例的演示焦點</strong>：員工 phishing → 內部管理工具接管 → 下游客戶 / 供應鏈傳導的 identity-chain 風險。重點在「員工身份」即「客戶風險面」的傳導邊界。其他 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>以釣魚或社交工程瞄準員工。</li>
<li>取得可登入的員工身份。</li>
<li>使用合法身份移動到高價值系統與資料。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>員工身份保護流程對社交工程韌性不足。</li>
<li>登入後的高敏感操作缺少額外驗證。</li>
<li>身分異常事件與快速隔離機制不夠緊密。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「員工帳號異常即時隔離」步驟，攻擊者會持續用合法會話做橫向移動，調查難度與影響面同步上升。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：高風險管理操作要求二次核准（multi-party approval、不只 MFA），mechanism 是讓單一帳號接管無法觸發影響客戶的決策。</li>
<li>日常：針對員工身份建立 <a href="/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook</a>（管理工具登入跨地理 / 跨裝置 / 異常時段）。</li>
<li>事故中：執行分批憑證輪替與權限縮減、控制 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>（前提是 token / 權限有 audit trail 可分批 scope）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/approval-flow-abuse/" data-link-title="7.R11.2 審核流程濫用" data-link-desc="說明審核節點為何會變成形式審核，進而放大高風險操作">核准流程濫用</a> —— 把員工身分 → 管理工具 → 客戶傳導的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> —— 把樣式轉成 tabletop 與 release gate 欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.twilio.com/en-us/blog/august-2022-social-engineering-attack">twilio.com</a></td>
          <td>官方</td>
          <td>攻擊入口、影響範圍、員工 phishing kit 第一手 telemetry</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>Scattered Spider / UNC3944 跨組織 TTP</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-sms-phishing-sim-swapping-ransomware/">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>Mandiant 對 SMS phishing / SIM swap 後續鏈 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.4 MGM 2023：身分流程被打穿後的營運中斷</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/mgm-2023-identity-lateral-impact/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 9 月，MGM 對外更新顯示，資安事件對營運造成明顯衝擊，反映出身份流程事件可快速轉為可用性問題。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：helpdesk social engineering → 高權限帳號接管 → 橫向擴散到核心系統 → 可用性 / 營運衝擊的 identity-to-availability chain。其他 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>以身分流程弱點取得初始落點。&lt;/li>
&lt;li>橫向影響多個內部系統。&lt;/li>
&lt;li>連帶影響面向客戶的服務可用性。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>身分事件與營運隔離界線不足。&lt;/li>
&lt;li>關鍵業務流程缺少快速降級方案。&lt;/li>
&lt;li>事件切換流程在高壓下不夠標準化。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「服務降級與切換劇本」，即使識別到攻擊路徑，也難以在可接受時間內維持核心服務。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：定義關鍵能力的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/degradation/" data-link-title="Degradation" data-link-desc="說明服務部分能力失效時如何保留核心功能與控制風險">degradation&lt;/a> 路徑，mechanism 是讓「身分受損」跟「營運停擺」解耦——不依賴攻擊期間能即時設計。&lt;/li>
&lt;li>日常：演練 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/failover/" data-link-title="Failover" data-link-desc="說明主要服務或節點失效時如何切換到備援能力">failover&lt;/a> 與回復時序（含 helpdesk 重置流程的 callback 驗證 / out-of-band 確認）。&lt;/li>
&lt;li>事故中：依 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-severity/" data-link-title="Incident Severity" data-link-desc="說明事故分級如何把產品影響轉成對應處置節奏">incident severity&lt;/a> 快速分級與跨團隊指揮（前提是事先有單一 IC 角色與升級 ladder）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/account-switching-abuse/" data-link-title="7.R11.4 帳號切換濫用" data-link-desc="說明多帳號切換為何容易形成會話混層與身份擴散">帳號切換濫用&lt;/a> —— helpdesk 重置 / 身分 takeover 的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> —— 把樣式轉成 tabletop 與 release gate / 回復欄位。&lt;/li>
&lt;/ul>
&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;a href="https://investors.mgmresorts.com/investors/news-releases/press-release-details/2023/MGM-Resorts-Provides-Cybersecurity-Incident-Update/default.aspx">investors.mgmresorts.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>事件對外揭露、影響範圍、復原時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>Scattered Spider / UNC3944 TTP、helpdesk 社交工程模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>Mandiant 對 helpdesk impersonation、SaaS 後續擴散 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 9 月，MGM 對外更新顯示，資安事件對營運造成明顯衝擊，反映出身份流程事件可快速轉為可用性問題。</p>
<p><strong>本案例的演示焦點</strong>：helpdesk social engineering → 高權限帳號接管 → 橫向擴散到核心系統 → 可用性 / 營運衝擊的 identity-to-availability chain。其他 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>以身分流程弱點取得初始落點。</li>
<li>橫向影響多個內部系統。</li>
<li>連帶影響面向客戶的服務可用性。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>身分事件與營運隔離界線不足。</li>
<li>關鍵業務流程缺少快速降級方案。</li>
<li>事件切換流程在高壓下不夠標準化。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「服務降級與切換劇本」，即使識別到攻擊路徑，也難以在可接受時間內維持核心服務。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：定義關鍵能力的 <a href="/blog/backend/knowledge-cards/degradation/" data-link-title="Degradation" data-link-desc="說明服務部分能力失效時如何保留核心功能與控制風險">degradation</a> 路徑，mechanism 是讓「身分受損」跟「營運停擺」解耦——不依賴攻擊期間能即時設計。</li>
<li>日常：演練 <a href="/blog/backend/knowledge-cards/failover/" data-link-title="Failover" data-link-desc="說明主要服務或節點失效時如何切換到備援能力">failover</a> 與回復時序（含 helpdesk 重置流程的 callback 驗證 / out-of-band 確認）。</li>
<li>事故中：依 <a href="/blog/backend/knowledge-cards/incident-severity/" data-link-title="Incident Severity" data-link-desc="說明事故分級如何把產品影響轉成對應處置節奏">incident severity</a> 快速分級與跨團隊指揮（前提是事先有單一 IC 角色與升級 ladder）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/account-switching-abuse/" data-link-title="7.R11.4 帳號切換濫用" data-link-desc="說明多帳號切換為何容易形成會話混層與身份擴散">帳號切換濫用</a> —— helpdesk 重置 / 身分 takeover 的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> —— 把樣式轉成 tabletop 與 release gate / 回復欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://investors.mgmresorts.com/investors/news-releases/press-release-details/2023/MGM-Resorts-Provides-Cybersecurity-Incident-Update/default.aspx">investors.mgmresorts.com</a></td>
          <td>官方</td>
          <td>事件對外揭露、影響範圍、復原時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>Scattered Spider / UNC3944 TTP、helpdesk 社交工程模式</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>Mandiant 對 helpdesk impersonation、SaaS 後續擴散 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.5 Microsoft Storm-0558 2023：簽章金鑰鏈與郵件存取</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/microsoft-storm-0558-2023-signing-key-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Storm-0558 事件揭露簽章金鑰治理一旦失效，攻擊者就能沿著身分信任鏈存取雲端郵件服務。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：簽章金鑰外洩 → 偽造可被驗證 token → 跨租戶身分接管的 federated trust chain 失效。屬於高層信任根（key material）類別、有別於前端社交工程或邊界漏洞。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>取得可用的簽章金鑰材料。&lt;/li>
&lt;li>偽造可被驗證的身分權杖。&lt;/li>
&lt;li>以合法樣態存取目標信箱與資料。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>簽章金鑰生命週期治理與隔離策略不足。&lt;/li>
&lt;li>權杖驗證邊界缺少跨服務一致性檢查。&lt;/li>
&lt;li>高風險身分事件的追查與升級節奏偏慢。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「跨租戶權杖異常立即升級」步驟，攻擊者可在低噪音條件下維持存取並擴大影響面。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：把簽章金鑰納入硬體保護與輪替節奏（HSM-bound、不可導出 / 強制輪替週期），mechanism 是讓金鑰即使被讀也無法搬離保護邊界。&lt;/li>
&lt;li>日常：監控 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 的異常關聯（跨租戶 token 出現相同 issuer 但不應跨域的軌跡）。&lt;/li>
&lt;li>事故中：同步執行金鑰收斂、權杖失效、受影響範圍比對（前提是 token validation 路徑可在 fleet 層級熱抽換 issuer）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/fp-federated-token-trust-drift/" data-link-title="7.R11.P13 聯邦 token 信任漂移" data-link-desc="說明跨平台聯邦 token 的來源與用途脫鉤如何放大傳導風險">Federated token trust drift&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用&lt;/a> —— 把跨租戶 token 驗證邊界失效的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成演練、輪替欄位與證據鏈。&lt;/li>
&lt;/ul>
&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;a href="https://www.microsoft.com/en-us/msrc/blog/2023/07/microsoft-mitigates-china-based-threat-actor-storm-0558-targeting-of-customer-email/">microsoft.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊鏈、影響範圍、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/resources-tools/resources/review-board-report-microsoft-exchange-online-incident">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>CSRB 對 cloud signing 治理的系統性檢討&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://msrc.microsoft.com/blog/2023/09/results-of-major-technical-investigations-for-storm-0558-key-acquisition/">msrc.microsoft.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>金鑰取得 root cause、token validation 邊界深度分析&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Storm-0558 事件揭露簽章金鑰治理一旦失效，攻擊者就能沿著身分信任鏈存取雲端郵件服務。</p>
<p><strong>本案例的演示焦點</strong>：簽章金鑰外洩 → 偽造可被驗證 token → 跨租戶身分接管的 federated trust chain 失效。屬於高層信任根（key material）類別、有別於前端社交工程或邊界漏洞。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>取得可用的簽章金鑰材料。</li>
<li>偽造可被驗證的身分權杖。</li>
<li>以合法樣態存取目標信箱與資料。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>簽章金鑰生命週期治理與隔離策略不足。</li>
<li>權杖驗證邊界缺少跨服務一致性檢查。</li>
<li>高風險身分事件的追查與升級節奏偏慢。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「跨租戶權杖異常立即升級」步驟，攻擊者可在低噪音條件下維持存取並擴大影響面。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：把簽章金鑰納入硬體保護與輪替節奏（HSM-bound、不可導出 / 強制輪替週期），mechanism 是讓金鑰即使被讀也無法搬離保護邊界。</li>
<li>日常：監控 <a href="/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 的異常關聯（跨租戶 token 出現相同 issuer 但不應跨域的軌跡）。</li>
<li>事故中：同步執行金鑰收斂、權杖失效、受影響範圍比對（前提是 token validation 路徑可在 fleet 層級熱抽換 issuer）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/fp-federated-token-trust-drift/" data-link-title="7.R11.P13 聯邦 token 信任漂移" data-link-desc="說明跨平台聯邦 token 的來源與用途脫鉤如何放大傳導風險">Federated token trust drift</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用</a> —— 把跨租戶 token 驗證邊界失效的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> + <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成演練、輪替欄位與證據鏈。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.microsoft.com/en-us/msrc/blog/2023/07/microsoft-mitigates-china-based-threat-actor-storm-0558-targeting-of-customer-email/">microsoft.com</a></td>
          <td>官方</td>
          <td>攻擊鏈、影響範圍、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/resources-tools/resources/review-board-report-microsoft-exchange-online-incident">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>CSRB 對 cloud signing 治理的系統性檢討</td>
      </tr>
      <tr>
          <td><a href="https://msrc.microsoft.com/blog/2023/09/results-of-major-technical-investigations-for-storm-0558-key-acquisition/">msrc.microsoft.com</a></td>
          <td>技術分析</td>
          <td>金鑰取得 root cause、token validation 邊界深度分析</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.6 Cloudflare 2023：供應商事件後的身分收斂</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/cloudflare-2023-okta-token-follow-through/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cloudflare 在 2023 年事件說明中展示了供應商端事件如何傳導到客戶端身分流程，並觸發大規模憑證與 token 收斂作業。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：上游 Identity Provider 事件 → 下游客戶側 token / session 收斂壓力的 identity-chain 風險傳導。其他 threat surface（直接 phishing / 邊界零時差 / 供應鏈植入）由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者先利用供應商支援流程取得線索。&lt;/li>
&lt;li>嘗試使用取得的資訊進入客戶端環境。&lt;/li>
&lt;li>透過 token、session 或憑證鏈路擴展存取。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>供應商事件觸發條件與內部 runbook 連動不足。&lt;/li>
&lt;li>高權限 token 的失效與輪替策略準備度不足。&lt;/li>
&lt;li>受影響資產盤點與證據保存流程分離。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「供應商事件即啟動全域 token 盤點」步驟，事件判讀會停在公告層，內部可利用憑證仍持續存在。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：為第三方事件設計獨立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與責任分工，mechanism 是讓供應商公告直接 trigger 內部盤點，不停在「閱讀公告」layer。&lt;/li>
&lt;li>日常：維護 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">playbook&lt;/a> 的憑證輪替優先級（依 token 範圍 / 受影響 tenant 分層、不是平均輪替）。&lt;/li>
&lt;li>事故中：先凍結高風險憑證、再分批恢復必要權限（前提是事先有 token 範圍 inventory、否則無法分批）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/fp-overscoped-third-party-token-grant/" data-link-title="7.R11.P12 第三方 token 授權範圍過寬" data-link-desc="說明第三方 token 授權範圍過寬如何放大供應商事件傳導">Overscoped 第三方 token grant&lt;/a> —— 把本案例的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> —— 把樣式轉成 tabletop 與 release gate 欄位。&lt;/li>
&lt;/ul>
&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;a href="https://blog.cloudflare.com/thanksgiving-2023-security-incident/">blog.cloudflare.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>客戶側偵測、即時回應、Zero Trust 與 hardware key 防守效果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-root-cause">sec.okta.com&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>上游事件 root cause、影響範圍、session token hijack 機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC3944 對 SaaS 攻擊 TTP、跨組織 chain 模式&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Cloudflare 在 2023 年事件說明中展示了供應商端事件如何傳導到客戶端身分流程，並觸發大規模憑證與 token 收斂作業。</p>
<p><strong>本案例的演示焦點</strong>：上游 Identity Provider 事件 → 下游客戶側 token / session 收斂壓力的 identity-chain 風險傳導。其他 threat surface（直接 phishing / 邊界零時差 / 供應鏈植入）由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者先利用供應商支援流程取得線索。</li>
<li>嘗試使用取得的資訊進入客戶端環境。</li>
<li>透過 token、session 或憑證鏈路擴展存取。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>供應商事件觸發條件與內部 runbook 連動不足。</li>
<li>高權限 token 的失效與輪替策略準備度不足。</li>
<li>受影響資產盤點與證據保存流程分離。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「供應商事件即啟動全域 token 盤點」步驟，事件判讀會停在公告層，內部可利用憑證仍持續存在。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：為第三方事件設計獨立 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與責任分工，mechanism 是讓供應商公告直接 trigger 內部盤點，不停在「閱讀公告」layer。</li>
<li>日常：維護 <a href="/blog/backend/knowledge-cards/playbook/" data-link-title="Playbook" data-link-desc="說明場景化處置腳本如何降低事故處理不確定性">playbook</a> 的憑證輪替優先級（依 token 範圍 / 受影響 tenant 分層、不是平均輪替）。</li>
<li>事故中：先凍結高風險憑證、再分批恢復必要權限（前提是事先有 token 範圍 inventory、否則無法分批）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/third-party-authorization-abuse/" data-link-title="7.R11.12 第三方授權濫用" data-link-desc="說明第三方授權流程為何容易成為供應商事件傳導節點">第三方授權濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/fp-overscoped-third-party-token-grant/" data-link-title="7.R11.P12 第三方 token 授權範圍過寬" data-link-desc="說明第三方 token 授權範圍過寬如何放大供應商事件傳導">Overscoped 第三方 token grant</a> —— 把本案例的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> —— 把樣式轉成 tabletop 與 release gate 欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://blog.cloudflare.com/thanksgiving-2023-security-incident/">blog.cloudflare.com</a></td>
          <td>官方</td>
          <td>客戶側偵測、即時回應、Zero Trust 與 hardware key 防守效果</td>
      </tr>
      <tr>
          <td><a href="https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-root-cause">sec.okta.com</a></td>
          <td>政府/監管</td>
          <td>上游事件 root cause、影響範圍、session token hijack 機制</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC3944 對 SaaS 攻擊 TTP、跨組織 chain 模式</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.7 Slack 2022：企業 token 與程式碼資產路徑</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/slack-2022-token-compromise/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Slack 2022 安全公告說明攻擊者透過員工帳號路徑接觸內部資產，突顯企業 token 與程式碼資產的連動風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：員工身分被取得後 → 內部 token / 程式碼資產的橫向擴散風險，重點在 token 範圍邊界與 audit signal 匯流的設計。其他 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>先透過社交工程取得員工憑證。&lt;/li>
&lt;li>進入內部工具並接觸 token 或程式碼資產。&lt;/li>
&lt;li>嘗試擴大到高價值系統或資料節點。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>員工身份遭濫用後的隔離速度不足。&lt;/li>
&lt;li>token 範圍與用途邊界定義不夠細緻。&lt;/li>
&lt;li>程式碼資產存取異常訊號未快速匯流。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「內部 token 快速撤銷」步驟，攻擊者會維持有效會話，讓追查與復原成本上升。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：把管理 token 分域並限制到最小權限（依用途切 audience，避免單一 token 跨多個敏感系統），mechanism 是讓單點接管不會直接通到所有資產。&lt;/li>
&lt;li>日常：建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook&lt;/a> 監控異常存取（repo 異常 clone、token 跨 IP / 跨 device 序列）。&lt;/li>
&lt;li>事故中：分層撤銷 token、並用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a> 框定影響面（前提是 token 有 inventory 可查 issuer / scope）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/invite-flow-abuse/" data-link-title="7.R11.1 邀請流程濫用" data-link-desc="說明邀請流程為何容易形成身份擴散與越權入口">邀請流程濫用&lt;/a> —— 把員工身分接管 → token / 資產存取的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成 token 治理欄位與證據鏈。&lt;/li>
&lt;/ul>
&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;a href="https://slack.com/blog/news/slack-security-update">slack.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊入口、影響範圍、token 處置時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨組織 social engineering TTP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC3944 對 SaaS / token 接管模式 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Slack 2022 安全公告說明攻擊者透過員工帳號路徑接觸內部資產，突顯企業 token 與程式碼資產的連動風險。</p>
<p><strong>本案例的演示焦點</strong>：員工身分被取得後 → 內部 token / 程式碼資產的橫向擴散風險，重點在 token 範圍邊界與 audit signal 匯流的設計。其他 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>先透過社交工程取得員工憑證。</li>
<li>進入內部工具並接觸 token 或程式碼資產。</li>
<li>嘗試擴大到高價值系統或資料節點。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>員工身份遭濫用後的隔離速度不足。</li>
<li>token 範圍與用途邊界定義不夠細緻。</li>
<li>程式碼資產存取異常訊號未快速匯流。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「內部 token 快速撤銷」步驟，攻擊者會維持有效會話，讓追查與復原成本上升。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：把管理 token 分域並限制到最小權限（依用途切 audience，避免單一 token 跨多個敏感系統），mechanism 是讓單點接管不會直接通到所有資產。</li>
<li>日常：建立 <a href="/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook</a> 監控異常存取（repo 異常 clone、token 跨 IP / 跨 device 序列）。</li>
<li>事故中：分層撤銷 token、並用 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 框定影響面（前提是 token 有 inventory 可查 issuer / scope）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/invite-flow-abuse/" data-link-title="7.R11.1 邀請流程濫用" data-link-desc="說明邀請流程為何容易形成身份擴散與越權入口">邀請流程濫用</a> —— 把員工身分接管 → token / 資產存取的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成 token 治理欄位與證據鏈。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://slack.com/blog/news/slack-security-update">slack.com</a></td>
          <td>官方</td>
          <td>攻擊入口、影響範圍、token 處置時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨組織 social engineering TTP</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC3944 對 SaaS / token 接管模式 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.1.8 Dropbox 2022：釣魚入侵與程式碼倉儲風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/dropbox-2022-code-repo-phishing-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/identity-access/dropbox-2022-code-repo-phishing-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Dropbox 2022 事件顯示員工帳號釣魚成功後，攻擊者可接觸私有程式碼倉儲與內部文件資產。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：員工 phishing → OAuth / 內部 SSO 接管 → 高敏感研發資產（私有 repo / 內部文件）橫向存取的身分鏈。其他 threat surface 由 supply-chain（artifact 植入）/ edge-exposure（邊界漏洞）/ data-exfiltration（量級外送壓力）案例分類承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>社交工程鎖定員工帳號。&lt;/li>
&lt;li>取得可登入的企業身份。&lt;/li>
&lt;li>存取程式碼倉儲與內部文件系統。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>員工端高風險登入驗證策略不足。&lt;/li>
&lt;li>研發資產保護缺少額外 step-up 驗證。&lt;/li>
&lt;li>身分異常與程式碼倉儲稽核串接不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「程式碼資產異常存取升級」步驟，攻擊者可在內部環境延長停留時間並擴大探索範圍。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：對高敏感 repo 操作要求強化 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication&lt;/a>（phishing-resistant 因子、step-up 不只密碼 + OTP），mechanism 是讓 phishing-collected 憑證在 step-up 環節失效。&lt;/li>
&lt;li>日常：將 repo 存取告警納入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 流程（異常 clone / push 模式、跨地理 / 跨裝置序列）。&lt;/li>
&lt;li>事故中：即時凍結可疑憑證與連線、保留時間軸證據（依賴 repo / SSO 事先有 audit log retention）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/invite-flow-abuse/" data-link-title="7.R11.1 邀請流程濫用" data-link-desc="說明邀請流程為何容易形成身份擴散與越權入口">邀請流程濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用&lt;/a> —— 把 phishing → OAuth grant → 委派擴散的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— 研發資產 mitigation 的 mechanism / 前提在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成 release gate 欄位與證據保存欄位。&lt;/li>
&lt;/ul>
&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;a href="https://dropbox.tech/security/a-security-update-on-code-repositories">dropbox.tech&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>phishing 入口、影響範圍、研發資產處置時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨組織 social engineering TTP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC3944 對 SaaS 攻擊模式、phishing kit telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Dropbox 2022 事件顯示員工帳號釣魚成功後，攻擊者可接觸私有程式碼倉儲與內部文件資產。</p>
<p><strong>本案例的演示焦點</strong>：員工 phishing → OAuth / 內部 SSO 接管 → 高敏感研發資產（私有 repo / 內部文件）橫向存取的身分鏈。其他 threat surface 由 supply-chain（artifact 植入）/ edge-exposure（邊界漏洞）/ data-exfiltration（量級外送壓力）案例分類承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>社交工程鎖定員工帳號。</li>
<li>取得可登入的企業身份。</li>
<li>存取程式碼倉儲與內部文件系統。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>員工端高風險登入驗證策略不足。</li>
<li>研發資產保護缺少額外 step-up 驗證。</li>
<li>身分異常與程式碼倉儲稽核串接不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「程式碼資產異常存取升級」步驟，攻擊者可在內部環境延長停留時間並擴大探索範圍。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：對高敏感 repo 操作要求強化 <a href="/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication</a>（phishing-resistant 因子、step-up 不只密碼 + OTP），mechanism 是讓 phishing-collected 憑證在 step-up 環節失效。</li>
<li>日常：將 repo 存取告警納入 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 流程（異常 clone / push 模式、跨地理 / 跨裝置序列）。</li>
<li>事故中：即時凍結可疑憑證與連線、保留時間軸證據（依賴 repo / SSO 事先有 audit log retention）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/invite-flow-abuse/" data-link-title="7.R11.1 邀請流程濫用" data-link-desc="說明邀請流程為何容易形成身份擴散與越權入口">邀請流程濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用</a> —— 把 phishing → OAuth grant → 委派擴散的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— 研發資產 mitigation 的 mechanism / 前提在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成 release gate 欄位與證據保存欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://dropbox.tech/security/a-security-update-on-code-repositories">dropbox.tech</a></td>
          <td>官方</td>
          <td>phishing 入口、影響範圍、研發資產處置時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨組織 social engineering TTP</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC3944 對 SaaS 攻擊模式、phishing kit telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.1 SolarWinds 2020：更新鏈被濫用</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/solarwinds-2020-sunburst/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2020 年公開的 SUNBURST 事件顯示，攻擊者透過供應鏈植入，將惡意行為包裹在合法更新流程中進入大量組織。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：合法更新管道被植入後、依賴下游對「已簽章 artifact」的高度信任進行長期潛伏與橫向擴散，屬於 build / release pipeline 上游 compromise 類別。身分鏈接管、邊界零時差、資料外送速率壓力等 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>滲透供應鏈節點。&lt;/li>
&lt;li>在合法交付流程植入惡意內容。&lt;/li>
&lt;li>依賴受害端對更新的高信任擴散。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>更新來源信任過於單點。&lt;/li>
&lt;li>行為監測難以區分合法元件與惡意利用。&lt;/li>
&lt;li>供應鏈異常事件缺少快速隔離流程。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「合法更新異常行為審查」，團隊會把事件視為一般系統活動，延長停留時間與清除成本。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：供應鏈節點做分層信任與簽章驗證（build provenance / SBOM / 簽章不只驗發行者、還驗 build 環境一致性），mechanism 是讓「合法簽章」不等於「未被植入」。&lt;/li>
&lt;li>日常：建立異常更新行為的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert&lt;/a>（受信任元件的非典型網路行為 / 異常 process 子鏈、不依賴單一 IoC）。&lt;/li>
&lt;li>事故中：切換受影響更新鏈、建立替代交付路徑與回復順序（前提是事先有 multi-source 更新策略、一鍵 cut-over 不能臨時設計）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成演練與控制欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的交付與簽章治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的供應鏈事件指揮流程。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故的失效樣式不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow 樣式），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.solarwinds.com/securityadvisory">solarwinds.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、植入時間軸、官方修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-352a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、檢測指引、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.mandiant.com/resources/blog/evasive-attacker-leverages-solarwinds-supply-chain-compromises">mandiant.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC2452 TTP、後門行為特徵、long-dwell evasion telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2020 年公開的 SUNBURST 事件顯示，攻擊者透過供應鏈植入，將惡意行為包裹在合法更新流程中進入大量組織。</p>
<p><strong>本案例的演示焦點</strong>：合法更新管道被植入後、依賴下游對「已簽章 artifact」的高度信任進行長期潛伏與橫向擴散，屬於 build / release pipeline 上游 compromise 類別。身分鏈接管、邊界零時差、資料外送速率壓力等 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>滲透供應鏈節點。</li>
<li>在合法交付流程植入惡意內容。</li>
<li>依賴受害端對更新的高信任擴散。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>更新來源信任過於單點。</li>
<li>行為監測難以區分合法元件與惡意利用。</li>
<li>供應鏈異常事件缺少快速隔離流程。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「合法更新異常行為審查」，團隊會把事件視為一般系統活動，延長停留時間與清除成本。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：供應鏈節點做分層信任與簽章驗證（build provenance / SBOM / 簽章不只驗發行者、還驗 build 環境一致性），mechanism 是讓「合法簽章」不等於「未被植入」。</li>
<li>日常：建立異常更新行為的 <a href="/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert</a>（受信任元件的非典型網路行為 / 異常 process 子鏈、不依賴單一 IoC）。</li>
<li>事故中：切換受影響更新鏈、建立替代交付路徑與回復順序（前提是事先有 multi-source 更新策略、一鍵 cut-over 不能臨時設計）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> + <a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成演練與控制欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的交付與簽章治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的供應鏈事件指揮流程。</li>
</ul>
<p>供應鏈類事故的失效樣式不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow 樣式），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.solarwinds.com/securityadvisory">solarwinds.com</a></td>
          <td>官方</td>
          <td>受影響版本、植入時間軸、官方修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-352a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、檢測指引、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://www.mandiant.com/resources/blog/evasive-attacker-leverages-solarwinds-supply-chain-compromises">mandiant.com</a></td>
          <td>技術分析</td>
          <td>UNC2452 TTP、後門行為特徵、long-dwell evasion telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.2 GitHub OAuth 2022：第三方 token 供應鏈風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/github-oauth-2022-token-supply-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2022 年 4 月，GitHub 公告指出攻擊者使用從第三方整合服務取得的 OAuth token 存取受影響組織資料。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：第三方整合 OAuth token 被竊 → 跨組織下游存取的 federated trust supply-chain 風險。重點在 OAuth scope / lifetime / inventory 設計、跟身分鏈接管 (identity-access category) 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊第三方整合節點。&lt;/li>
&lt;li>取得可用 OAuth token。&lt;/li>
&lt;li>使用 token 存取下游客戶資產。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>token 權限範圍過寬。&lt;/li>
&lt;li>token 生命周期偏長，撤銷速度慢。&lt;/li>
&lt;li>整合關係資產盤點與監控不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「第三方 token 全域盤點與快速撤銷」，事件發生後仍會留下可用 token，形成二次入侵窗口。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：採最小權限 token 與明確用途分域（OAuth scope 不用 catch-all、按 audience 切），mechanism 是讓單個 token 接管不會通往無關資產。&lt;/li>
&lt;li>日常：建立第三方整合清單與失效期限巡檢（含 token 上次使用時間、長期未用就主動失效）。&lt;/li>
&lt;li>事故中：依清單自動化撤銷、輪替、補授權（前提是 token issuer 提供 bulk revocation API）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> —— 把樣式轉成 token 治理欄位與輪替演練。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">backend/04-observability&lt;/a> 的第三方整合監測、&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的部署 token 治理。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/">github.blog&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>OAuth token 被竊起點、影響組織範圍、初步處置時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://github.blog/2022-12-08-notice-of-security-incident/">github.blog&lt;/a>&lt;/td>
 &lt;td>官方延伸&lt;/td>
 &lt;td>後續事件、跨整合影響評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨組織 OAuth abuse / federated chain TTP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2022 年 4 月，GitHub 公告指出攻擊者使用從第三方整合服務取得的 OAuth token 存取受影響組織資料。</p>
<p><strong>本案例的演示焦點</strong>：第三方整合 OAuth token 被竊 → 跨組織下游存取的 federated trust supply-chain 風險。重點在 OAuth scope / lifetime / inventory 設計、跟身分鏈接管 (identity-access category) 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊第三方整合節點。</li>
<li>取得可用 OAuth token。</li>
<li>使用 token 存取下游客戶資產。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>token 權限範圍過寬。</li>
<li>token 生命周期偏長，撤銷速度慢。</li>
<li>整合關係資產盤點與監控不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「第三方 token 全域盤點與快速撤銷」，事件發生後仍會留下可用 token，形成二次入侵窗口。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：採最小權限 token 與明確用途分域（OAuth scope 不用 catch-all、按 audience 切），mechanism 是讓單個 token 接管不會通往無關資產。</li>
<li>日常：建立第三方整合清單與失效期限巡檢（含 token 上次使用時間、長期未用就主動失效）。</li>
<li>事故中：依清單自動化撤銷、輪替、補授權（前提是 token issuer 提供 bulk revocation API）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> + <a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> —— 把樣式轉成 token 治理欄位與輪替演練。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">backend/04-observability</a> 的第三方整合監測、<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的部署 token 治理。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/">github.blog</a></td>
          <td>官方</td>
          <td>OAuth token 被竊起點、影響組織範圍、初步處置時序</td>
      </tr>
      <tr>
          <td><a href="https://github.blog/2022-12-08-notice-of-security-incident/">github.blog</a></td>
          <td>官方延伸</td>
          <td>後續事件、跨整合影響評估</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨組織 OAuth abuse / federated chain TTP</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.3 CircleCI 2023：CI secrets 輪替壓力</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/circleci-2023-secrets-rotation/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 1 月，CircleCI 公告指出攻擊者透過員工端點入侵影響生產環境，並要求客戶輪替 secrets。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：CI 平台側被入侵 → 客戶 secrets 整批暴露 → 下游全面輪替壓力的 secrets-blast-radius 事件。重點在 secrets 範圍 / 輪替成本與 inventory 的設計。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>以端點路徑取得平台側存取能力。&lt;/li>
&lt;li>觸及集中管理的 secrets。&lt;/li>
&lt;li>把風險擴散到客戶部署環境。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>CI secrets 集中化且缺少分域隔離。&lt;/li>
&lt;li>輪替流程成本高，導致執行延遲。&lt;/li>
&lt;li>客戶端難以快速判斷最小必要輪替範圍。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「分批輪替與優先級排序」流程，團隊要在壓力下做全面輪替，容易造成服務中斷或遺漏。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：定義 secrets 分級與依賴地圖（依 blast radius 分層、不只依名稱），mechanism 是讓事件期間的輪替能依風險排序、不靠 ad-hoc 判斷。&lt;/li>
&lt;li>日常：定期演練 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明事故期間如何判斷回滾、回切與暫停變更">rollback strategy&lt;/a> 與 secrets 更新（含「假設整個 CI vendor 受損」的 fire drill）。&lt;/li>
&lt;li>事故中：按分級快速輪替、並記錄 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR&lt;/a>（前提是事先有 secrets inventory 跟 owner mapping）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> —— 把樣式轉成輪替演練、credential 治理與回復欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的 CI/CD 機制、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的止血與回復順序。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://circleci.com/blog/jan-4-2023-incident-report/">circleci.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊入口、影響範圍、初步輪替建議時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://circleci.com/blog/january-12-2023-security-alert/">circleci.com&lt;/a>&lt;/td>
 &lt;td>官方延伸&lt;/td>
 &lt;td>post-incident 細節、root cause、跨客戶影響評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨組織 social engineering / endpoint compromise TTP&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 1 月，CircleCI 公告指出攻擊者透過員工端點入侵影響生產環境，並要求客戶輪替 secrets。</p>
<p><strong>本案例的演示焦點</strong>：CI 平台側被入侵 → 客戶 secrets 整批暴露 → 下游全面輪替壓力的 secrets-blast-radius 事件。重點在 secrets 範圍 / 輪替成本與 inventory 的設計。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>以端點路徑取得平台側存取能力。</li>
<li>觸及集中管理的 secrets。</li>
<li>把風險擴散到客戶部署環境。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>CI secrets 集中化且缺少分域隔離。</li>
<li>輪替流程成本高，導致執行延遲。</li>
<li>客戶端難以快速判斷最小必要輪替範圍。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「分批輪替與優先級排序」流程，團隊要在壓力下做全面輪替，容易造成服務中斷或遺漏。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：定義 secrets 分級與依賴地圖（依 blast radius 分層、不只依名稱），mechanism 是讓事件期間的輪替能依風險排序、不靠 ad-hoc 判斷。</li>
<li>日常：定期演練 <a href="/blog/backend/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明事故期間如何判斷回滾、回切與暫停變更">rollback strategy</a> 與 secrets 更新（含「假設整個 CI vendor 受損」的 fire drill）。</li>
<li>事故中：按分級快速輪替、並記錄 <a href="/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR</a>（前提是事先有 secrets inventory 跟 owner mapping）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理</a> + <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> —— 把樣式轉成輪替演練、credential 治理與回復欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的 CI/CD 機制、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的止血與回復順序。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://circleci.com/blog/jan-4-2023-incident-report/">circleci.com</a></td>
          <td>官方</td>
          <td>攻擊入口、影響範圍、初步輪替建議時序</td>
      </tr>
      <tr>
          <td><a href="https://circleci.com/blog/january-12-2023-security-alert/">circleci.com</a></td>
          <td>官方延伸</td>
          <td>post-incident 細節、root cause、跨客戶影響評估</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨組織 social engineering / endpoint compromise TTP</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.4 XZ Backdoor 2024：開源供應鏈長期滲透</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/xz-backdoor-2024-open-source-supply-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2024 年 3 月，XZ Utils 事件揭露開源供應鏈可被長期滲透並在釋出流程埋入後門，對基礎設施信任鏈造成直接衝擊。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：開源 maintainer 角色被長期社交滲透 → 釋出流程嵌入後門 → 跨 Linux 發行版下游擴散的 human-supply-chain 攻擊。重點在 maintainer trust governance、跟 build pipeline / artifact provenance 攻擊形成互補。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>長期滲透維護流程。&lt;/li>
&lt;li>在釋出包鏈條加入惡意邏輯。&lt;/li>
&lt;li>透過下游發行與部署流程擴散風險。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>開源維護與釋出治理缺少獨立覆核。&lt;/li>
&lt;li>下游對上游釋出信任過高。&lt;/li>
&lt;li>供應鏈檢測流程常延後辨識異常組件行為。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「上游重大事件觸發的版本凍結與風險重評」，下游仍可能將高風險版本推進正式環境。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：關鍵依賴建立雙人覆核與來源驗證（不只 hash 比對、檢查 release-tarball 跟 git tag 的差異），mechanism 是讓 maintainer 單點失守不會直接通到下游。&lt;/li>
&lt;li>日常：維護套件清單與影響面地圖（含 transitive 依賴、build-time vs runtime 區分）。&lt;/li>
&lt;li>事故中：啟動版本凍結、替代版本切換與復測流程（前提是事先有「該套件 unavailable 也能 build」的 fallback 評估）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> —— 把樣式轉成 SBOM 演練、版本凍結欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的依賴治理、&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability&lt;/a> 的變更風險控制。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="http://www.openwall.com/lists/oss-security/2024/03/29/4">openwall.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>第一手揭露、後門技術細節、發現時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2024/03/29/reported-supply-chain-compromise-affecting-xz-utils-data-compression-library-cve-2024-3094">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3094">nvd.nist.gov/CVE-2024-3094&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、build-time injection 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2024 年 3 月，XZ Utils 事件揭露開源供應鏈可被長期滲透並在釋出流程埋入後門，對基礎設施信任鏈造成直接衝擊。</p>
<p><strong>本案例的演示焦點</strong>：開源 maintainer 角色被長期社交滲透 → 釋出流程嵌入後門 → 跨 Linux 發行版下游擴散的 human-supply-chain 攻擊。重點在 maintainer trust governance、跟 build pipeline / artifact provenance 攻擊形成互補。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>長期滲透維護流程。</li>
<li>在釋出包鏈條加入惡意邏輯。</li>
<li>透過下游發行與部署流程擴散風險。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>開源維護與釋出治理缺少獨立覆核。</li>
<li>下游對上游釋出信任過高。</li>
<li>供應鏈檢測流程常延後辨識異常組件行為。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「上游重大事件觸發的版本凍結與風險重評」，下游仍可能將高風險版本推進正式環境。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：關鍵依賴建立雙人覆核與來源驗證（不只 hash 比對、檢查 release-tarball 跟 git tag 的差異），mechanism 是讓 maintainer 單點失守不會直接通到下游。</li>
<li>日常：維護套件清單與影響面地圖（含 transitive 依賴、build-time vs runtime 區分）。</li>
<li>事故中：啟動版本凍結、替代版本切換與復測流程（前提是事先有「該套件 unavailable 也能 build」的 fallback 評估）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> —— 把樣式轉成 SBOM 演練、版本凍結欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的依賴治理、<a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability</a> 的變更風險控制。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="http://www.openwall.com/lists/oss-security/2024/03/29/4">openwall.com</a></td>
          <td>官方</td>
          <td>第一手揭露、後門技術細節、發現時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2024/03/29/reported-supply-chain-compromise-affecting-xz-utils-data-compression-library-cve-2024-3094">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3094">nvd.nist.gov/CVE-2024-3094</a></td>
          <td>技術分析</td>
          <td>CVE 細節、build-time injection 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>TeamCity CVE-2023-42793 事件顯示 CI 平台入口漏洞會直接衝擊建置與交付信任鏈。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：CI 管理介面 auth bypass → build / artifact 接管 → 下游交付污染的 CI-entrypoint supply-chain 鏈。跟 2024 雙漏洞事件互補、共同說明 CI 平台暴露面治理。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描並利用 TeamCity 管理入口漏洞。&lt;/li>
&lt;li>取得 CI 管理權限或執行能力。&lt;/li>
&lt;li>影響 build artifact 與下游部署流程。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>CI 管理介面暴露面過高。&lt;/li>
&lt;li>建置輸出完整性驗證不足。&lt;/li>
&lt;li>平台事件與下游部署凍結流程連動不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「CI 平台事件觸發部署凍結」步驟，受污染 artifact 可能持續流向正式環境。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：建立 CI 管理入口隔離與最小存取權限（內網 only、強制 MFA），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。&lt;/li>
&lt;li>日常：對 build pipeline 建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert&lt;/a>（unauthorized config change、異常 build trigger）。&lt;/li>
&lt;li>事故中：暫停發佈、驗證 artifact、按優先級恢復（前提是 artifact 有 build provenance、可追溯產生時間）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> —— 把樣式轉成 CI 凍結演練、漏洞處理欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的 CI/CD 信任鏈、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的凍結與回復流程。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://blog.jetbrains.com/teamcity/2023/09/cve-2023-42793-vulnerability-post-mortem">blog.jetbrains.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補時序&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-347a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-42793">nvd.nist.gov/CVE-2023-42793&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、auth bypass 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>TeamCity CVE-2023-42793 事件顯示 CI 平台入口漏洞會直接衝擊建置與交付信任鏈。</p>
<p><strong>本案例的演示焦點</strong>：CI 管理介面 auth bypass → build / artifact 接管 → 下游交付污染的 CI-entrypoint supply-chain 鏈。跟 2024 雙漏洞事件互補、共同說明 CI 平台暴露面治理。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描並利用 TeamCity 管理入口漏洞。</li>
<li>取得 CI 管理權限或執行能力。</li>
<li>影響 build artifact 與下游部署流程。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>CI 管理介面暴露面過高。</li>
<li>建置輸出完整性驗證不足。</li>
<li>平台事件與下游部署凍結流程連動不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「CI 平台事件觸發部署凍結」步驟，受污染 artifact 可能持續流向正式環境。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：建立 CI 管理入口隔離與最小存取權限（內網 only、強制 MFA），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。</li>
<li>日常：對 build pipeline 建立 <a href="/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert</a>（unauthorized config change、異常 build trigger）。</li>
<li>事故中：暫停發佈、驗證 artifact、按優先級恢復（前提是 artifact 有 build provenance、可追溯產生時間）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> + <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> —— 把樣式轉成 CI 凍結演練、漏洞處理欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的 CI/CD 信任鏈、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的凍結與回復流程。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://blog.jetbrains.com/teamcity/2023/09/cve-2023-42793-vulnerability-post-mortem">blog.jetbrains.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補時序</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-347a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-42793">nvd.nist.gov/CVE-2023-42793</a></td>
          <td>技術分析</td>
          <td>CVE 細節、auth bypass 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.6 ScreenConnect 2024：RMM 平台入口與下游擴散</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/screenconnect-cve-2024-1709-rmm-entrypoint/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/screenconnect-cve-2024-1709-rmm-entrypoint/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>ConnectWise ScreenConnect CVE-2024-1709 事件突顯 RMM 平台事件會沿著維運供應鏈向多客戶擴散。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：RMM 平台 auth bypass → 維運通道接管 → 客戶環境同時受影響的 fan-out 模式。跟 Kaseya 類似但 entrypoint 是 web admin 認證繞過、屬 entrypoint-side supply-chain 變體。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>利用 RMM 平台漏洞取得管理能力。&lt;/li>
&lt;li>接觸維運節點與客戶端連線能力。&lt;/li>
&lt;li>透過既有信任路徑擴大影響範圍。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>RMM 節點權限集中且邊界分離不足。&lt;/li>
&lt;li>客戶環境缺少獨立限制與跳板治理。&lt;/li>
&lt;li>事件時的連線停用與重授權流程準備度不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「RMM 事件後連線總開關」步驟，攻擊者可沿既有管理通道持續操作下游資產。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：將 RMM 權限與客戶域做嚴格分域（管理介面強制 MFA + 來源限制、客戶側端點不直連管理平面），mechanism 是讓平台漏洞不直接通到客戶資產。&lt;/li>
&lt;li>日常：建立遠端管理連線基線與稽核節奏（哪個 operator 在哪個時段對哪個客戶進行哪類操作的 audit trail）。&lt;/li>
&lt;li>事故中：先關閉高風險連線、再分批重啟授權（前提是事先有「總開關」設計、不臨時找)。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern&lt;/a> —— 把樣式轉成漏洞處理、跨組織 owner 分工。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的跨團隊升級路由、&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的維運平台治理。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.connectwise.com/company/trust/security-bulletins/connectwise-screenconnect-23.9.8">connectwise.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2024/02/22/cisa-adds-one-known-exploited-connectwise-vulnerability-cve-2024-1709-catalog">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-1709">nvd.nist.gov/CVE-2024-1709&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、auth bypass 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>ConnectWise ScreenConnect CVE-2024-1709 事件突顯 RMM 平台事件會沿著維運供應鏈向多客戶擴散。</p>
<p><strong>本案例的演示焦點</strong>：RMM 平台 auth bypass → 維運通道接管 → 客戶環境同時受影響的 fan-out 模式。跟 Kaseya 類似但 entrypoint 是 web admin 認證繞過、屬 entrypoint-side supply-chain 變體。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>利用 RMM 平台漏洞取得管理能力。</li>
<li>接觸維運節點與客戶端連線能力。</li>
<li>透過既有信任路徑擴大影響範圍。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>RMM 節點權限集中且邊界分離不足。</li>
<li>客戶環境缺少獨立限制與跳板治理。</li>
<li>事件時的連線停用與重授權流程準備度不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「RMM 事件後連線總開關」步驟，攻擊者可沿既有管理通道持續操作下游資產。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：將 RMM 權限與客戶域做嚴格分域（管理介面強制 MFA + 來源限制、客戶側端點不直連管理平面），mechanism 是讓平台漏洞不直接通到客戶資產。</li>
<li>日常：建立遠端管理連線基線與稽核節奏（哪個 operator 在哪個時段對哪個客戶進行哪類操作的 audit trail）。</li>
<li>事故中：先關閉高風險連線、再分批重啟授權（前提是事先有「總開關」設計、不臨時找)。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern</a> —— 把樣式轉成漏洞處理、跨組織 owner 分工。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的跨團隊升級路由、<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的維運平台治理。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.connectwise.com/company/trust/security-bulletins/connectwise-screenconnect-23.9.8">connectwise.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2024/02/22/cisa-adds-one-known-exploited-connectwise-vulnerability-cve-2024-1709-catalog">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-1709">nvd.nist.gov/CVE-2024-1709</a></td>
          <td>技術分析</td>
          <td>CVE 細節、auth bypass 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.7 Log4Shell 2021：共用元件風險與修補鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/log4shell-cve-2021-44228-component-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Log4Shell 事件說明共用元件漏洞可在短時間內跨服務擴散，形成大規模修補與驗證壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：共用元件 zero-day → 跨服務同時暴露 → SBOM / 依賴 inventory 緊急檢索 → 大規模分批修補的 transitive-dependency 危機。重點在依賴可見性與分批修補節奏。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者偵測含漏洞元件的可達服務。&lt;/li>
&lt;li>透過日誌處理路徑觸發遠端執行。&lt;/li>
&lt;li>沿著相依資產清單擴大利用範圍。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>相依套件盤點與版本可見性不足。&lt;/li>
&lt;li>修補節奏缺少業務優先級路由。&lt;/li>
&lt;li>修補完成後驗證流程覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補後主動復測」步驟，團隊會把版本更新等同風險關閉，留下可利用殘餘面。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：維護關鍵套件 SBOM 與影響面對照（含 transitive 依賴、不只直接 import），mechanism 是讓事件期間能在分鐘級回答「我們有沒有在用」。&lt;/li>
&lt;li>日常：對高風險元件建立固定巡檢節奏（component criticality + reachability 分層、不全面平均掃）。&lt;/li>
&lt;li>事故中：按服務層級修補並追蹤 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR&lt;/a>（前提是修補後有 functional + security 雙重 verify、不只 build pass）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> —— 把樣式轉成 SBOM 演練、漏洞分批處理欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的依賴治理與版本策略、&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability&lt;/a> 的回復與驗證節奏。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://logging.apache.org/log4j/2.x/security.html">logging.apache.org&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、修補節奏、緩解 / patch 區別&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/news/apache-log4j-vulnerability-guidance-0">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨機構處置建議、scanning 工具清單&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44228">nvd.nist.gov/CVE-2021-44228&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、JNDI lookup 利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Log4Shell 事件說明共用元件漏洞可在短時間內跨服務擴散，形成大規模修補與驗證壓力。</p>
<p><strong>本案例的演示焦點</strong>：共用元件 zero-day → 跨服務同時暴露 → SBOM / 依賴 inventory 緊急檢索 → 大規模分批修補的 transitive-dependency 危機。重點在依賴可見性與分批修補節奏。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者偵測含漏洞元件的可達服務。</li>
<li>透過日誌處理路徑觸發遠端執行。</li>
<li>沿著相依資產清單擴大利用範圍。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>相依套件盤點與版本可見性不足。</li>
<li>修補節奏缺少業務優先級路由。</li>
<li>修補完成後驗證流程覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補後主動復測」步驟，團隊會把版本更新等同風險關閉，留下可利用殘餘面。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：維護關鍵套件 SBOM 與影響面對照（含 transitive 依賴、不只直接 import），mechanism 是讓事件期間能在分鐘級回答「我們有沒有在用」。</li>
<li>日常：對高風險元件建立固定巡檢節奏（component criticality + reachability 分層、不全面平均掃）。</li>
<li>事故中：按服務層級修補並追蹤 <a href="/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR</a>（前提是修補後有 functional + security 雙重 verify、不只 build pass）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> —— 把樣式轉成 SBOM 演練、漏洞分批處理欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的依賴治理與版本策略、<a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability</a> 的回復與驗證節奏。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://logging.apache.org/log4j/2.x/security.html">logging.apache.org</a></td>
          <td>官方</td>
          <td>受影響版本、修補節奏、緩解 / patch 區別</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/news/apache-log4j-vulnerability-guidance-0">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨機構處置建議、scanning 工具清單</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44228">nvd.nist.gov/CVE-2021-44228</a></td>
          <td>技術分析</td>
          <td>CVE 細節、JNDI lookup 利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.8 3CX 2023：桌面軟體更新鏈攻擊</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/3cx-2023-desktopapp-supply-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>3CX 2023 事件展示桌面軟體更新鏈受攻擊後，企業端點會同步暴露於供應鏈風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：桌面應用更新管道被植入 → 企業端點受信任安裝 → 端點成為後續控制節點的 build / release pipeline 上游 compromise。屬於跨平台桌面更新鏈類別、跟 server-side artifact 攻擊鏈互補。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者污染桌面應用程式交付流程。&lt;/li>
&lt;li>受影響版本進入企業端點。&lt;/li>
&lt;li>端點成為後續滲透與控制節點。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>更新來源信任缺少多重驗證。&lt;/li>
&lt;li>端點行為異常檢測與更新事件未連動。&lt;/li>
&lt;li>事件時版本凍結與替代方案準備不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「供應鏈事件即凍結更新版本」步驟，受影響版本仍會在內部持續擴散。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：建立更新簽章與來源完整性檢查（簽章鏈到 build provenance、不只發行者公鑰），mechanism 是讓「合法簽章」不等於「未被植入」。&lt;/li>
&lt;li>日常：將端點異常與更新事件關聯到同一告警流程（受信任應用 spawn 異常 process / 異常網路 callback）。&lt;/li>
&lt;li>事故中：凍結版本、隔離端點、驗證恢復清單（前提是 endpoint inventory 可在事件期間快速 query 已安裝版本）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成演練與控制欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的交付鏈風險治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的隔離與恢復協作。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.3cx.com/blog/news/security-alert-update/">3cx.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、植入時間軸、官方修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2023/03/30/supply-chain-attack-against-3cxdesktopapp">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、檢測指引&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.sentinelone.com/blog/smoothoperator-ongoing-campaign-trojanizes-3cxdesktopapp-in-a-supply-chain-attack/">sentinelone.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>SmoothOperator campaign TTP、後門行為特徵 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>3CX 2023 事件展示桌面軟體更新鏈受攻擊後，企業端點會同步暴露於供應鏈風險。</p>
<p><strong>本案例的演示焦點</strong>：桌面應用更新管道被植入 → 企業端點受信任安裝 → 端點成為後續控制節點的 build / release pipeline 上游 compromise。屬於跨平台桌面更新鏈類別、跟 server-side artifact 攻擊鏈互補。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者污染桌面應用程式交付流程。</li>
<li>受影響版本進入企業端點。</li>
<li>端點成為後續滲透與控制節點。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>更新來源信任缺少多重驗證。</li>
<li>端點行為異常檢測與更新事件未連動。</li>
<li>事件時版本凍結與替代方案準備不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「供應鏈事件即凍結更新版本」步驟，受影響版本仍會在內部持續擴散。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：建立更新簽章與來源完整性檢查（簽章鏈到 build provenance、不只發行者公鑰），mechanism 是讓「合法簽章」不等於「未被植入」。</li>
<li>日常：將端點異常與更新事件關聯到同一告警流程（受信任應用 spawn 異常 process / 異常網路 callback）。</li>
<li>事故中：凍結版本、隔離端點、驗證恢復清單（前提是 endpoint inventory 可在事件期間快速 query 已安裝版本）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成演練與控制欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的交付鏈風險治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的隔離與恢復協作。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.3cx.com/blog/news/security-alert-update/">3cx.com</a></td>
          <td>官方</td>
          <td>受影響版本、植入時間軸、官方修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2023/03/30/supply-chain-attack-against-3cxdesktopapp">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、檢測指引</td>
      </tr>
      <tr>
          <td><a href="https://www.sentinelone.com/blog/smoothoperator-ongoing-campaign-trojanizes-3cxdesktopapp-in-a-supply-chain-attack/">sentinelone.com</a></td>
          <td>技術分析</td>
          <td>SmoothOperator campaign TTP、後門行為特徵 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.9 Kaseya VSA 2021：MSP 供應鏈擴散路徑</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/kaseya-vsa-2021-msp-ransomware-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/kaseya-vsa-2021-msp-ransomware-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Kaseya VSA 2021 事件指出 MSP 管理平台若失守，攻擊可沿著託管關係快速擴展到多個客戶環境。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：MSP / RMM 管理平面被入侵 → 透過自動化能力批次下發 → 多客戶同時感染的 fan-out 供應鏈擴散。重點在「管理平面權限範圍」與「客戶分域隔離」設計。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊管理平台入口。&lt;/li>
&lt;li>透過自動化管理能力下發惡意行為。&lt;/li>
&lt;li>連鎖影響多個下游客戶系統。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理平面與客戶環境隔離不足。&lt;/li>
&lt;li>自動化任務缺少高風險動作保護。&lt;/li>
&lt;li>多租戶事件協調流程準備不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「跨客戶分批隔離」步驟，事件會在同一時間窗內形成大規模連鎖衝擊。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：限制管理平面高風險任務範圍（破壞性動作要求 multi-party approval / 批次上限），mechanism 是讓單點接管不會立刻 fan-out 到所有客戶。&lt;/li>
&lt;li>日常：建立多租戶事件通知與處置模板（含跨時區、跨法域的客戶通報路由）。&lt;/li>
&lt;li>事故中：先分域隔離、再啟動客戶側回復計畫（前提是事先有客戶分組與隔離開關）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> —— 把樣式轉成多租戶演練、回復欄位與漏洞處理流程。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的跨組織通訊節奏、&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的多租戶部署治理。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://helpdesk.kaseya.com/hc/en-gb/articles/4403440684689">helpdesk.kaseya.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、修補時序、客戶通報節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-209a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、檢測指引、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-30116">nvd.nist.gov/CVE-2021-30116&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、authenticated bypass 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Kaseya VSA 2021 事件指出 MSP 管理平台若失守，攻擊可沿著託管關係快速擴展到多個客戶環境。</p>
<p><strong>本案例的演示焦點</strong>：MSP / RMM 管理平面被入侵 → 透過自動化能力批次下發 → 多客戶同時感染的 fan-out 供應鏈擴散。重點在「管理平面權限範圍」與「客戶分域隔離」設計。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊管理平台入口。</li>
<li>透過自動化管理能力下發惡意行為。</li>
<li>連鎖影響多個下游客戶系統。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理平面與客戶環境隔離不足。</li>
<li>自動化任務缺少高風險動作保護。</li>
<li>多租戶事件協調流程準備不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「跨客戶分批隔離」步驟，事件會在同一時間窗內形成大規模連鎖衝擊。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：限制管理平面高風險任務範圍（破壞性動作要求 multi-party approval / 批次上限），mechanism 是讓單點接管不會立刻 fan-out 到所有客戶。</li>
<li>日常：建立多租戶事件通知與處置模板（含跨時區、跨法域的客戶通報路由）。</li>
<li>事故中：先分域隔離、再啟動客戶側回復計畫（前提是事先有客戶分組與隔離開關）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> + <a href="/blog/backend/07-security-data-protection/workload-identity-and-federated-trust/" data-link-title="7.10 Workload Identity 與聯邦信任邊界" data-link-desc="定義非人類身份、跨平台信任與短時憑證治理問題">7.5 工作負載身份與 federated trust</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> —— 把樣式轉成多租戶演練、回復欄位與漏洞處理流程。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的跨組織通訊節奏、<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的多租戶部署治理。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://helpdesk.kaseya.com/hc/en-gb/articles/4403440684689">helpdesk.kaseya.com</a></td>
          <td>官方</td>
          <td>受影響版本、修補時序、客戶通報節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-209a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、檢測指引、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-30116">nvd.nist.gov/CVE-2021-30116</a></td>
          <td>技術分析</td>
          <td>CVE 細節、authenticated bypass 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.1 MOVEit 2023：外網檔案服務批量外送</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/moveit-2023-mass-exfiltration/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/moveit-2023-mass-exfiltration/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 5 到 6 月，MOVEit Transfer 事件顯示，對外檔案傳輸服務在漏洞公開後可被快速批量利用並造成資料外送。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描外網可達 MFT 入口。&lt;/li>
&lt;li>利用漏洞取得存取能力。&lt;/li>
&lt;li>蒐集與外送高價值資料。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>對外入口缺少最小暴露設計。&lt;/li>
&lt;li>漏洞修補與隔離流程慢於攻擊自動化。&lt;/li>
&lt;li>外送行為偵測粒度不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「漏洞公告即觸發入口隔離」流程，等待正式修補期間仍會被持續掃描與利用。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：對外服務建立即時隔離開關。&lt;/li>
&lt;li>日常：監控大批量匯出與異常下載模式。&lt;/li>
&lt;li>事故中：先隔離入口，再做修補與回復。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.progress.com/trust-center/moveit-transfer-and-moveit-cloud-vulnerability">progress.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-158a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-34362">nvd.nist.gov/CVE-2023-34362&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 5 到 6 月，MOVEit Transfer 事件顯示，對外檔案傳輸服務在漏洞公開後可被快速批量利用並造成資料外送。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描外網可達 MFT 入口。</li>
<li>利用漏洞取得存取能力。</li>
<li>蒐集與外送高價值資料。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>對外入口缺少最小暴露設計。</li>
<li>漏洞修補與隔離流程慢於攻擊自動化。</li>
<li>外送行為偵測粒度不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「漏洞公告即觸發入口隔離」流程，等待正式修補期間仍會被持續掃描與利用。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：對外服務建立即時隔離開關。</li>
<li>日常：監控大批量匯出與異常下載模式。</li>
<li>事故中：先隔離入口，再做修補與回復。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.progress.com/trust-center/moveit-transfer-and-moveit-cloud-vulnerability">progress.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-158a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-34362">nvd.nist.gov/CVE-2023-34362</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.2 Ivanti 2024：CVE-2023-46805/2024-21887 VPN 邊界漏洞鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/ivanti-2024-vpn-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/ivanti-2024-vpn-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2024 年初，Ivanti Connect Secure 相關公告顯示攻擊者可串接 CVE-2023-46805 與 CVE-2024-21887 進行認證繞過與遠端執行，並帶來持久化風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-46805 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描可達 VPN 邊界。&lt;/li>
&lt;li>利用漏洞鏈取得初始控制。&lt;/li>
&lt;li>建立持續存取與後續移動路徑。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>邊界設備長期暴露且承載關鍵流量。&lt;/li>
&lt;li>修補後狀態驗證流程不足。&lt;/li>
&lt;li>清除與重建步驟缺少標準化程序。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「修補後完整驗證」步驟，系統可能在已修補狀態下仍保留可利用持久化痕跡。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：高風險邊界設備準備替代路徑。&lt;/li>
&lt;li>日常：建立邊界設備健康與變更基線。&lt;/li>
&lt;li>事故中：執行隔離、重建、憑證輪替三段流程。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.ivanti.com/blog/security-update-for-ivanti-connect-secure-and-ivanti-policy-secure-gateways">ivanti.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa24-060b">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-46805">nvd.nist.gov/CVE-2023-46805&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21887">nvd.nist.gov/CVE-2024-21887&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2024 年初，Ivanti Connect Secure 相關公告顯示攻擊者可串接 CVE-2023-46805 與 CVE-2024-21887 進行認證繞過與遠端執行，並帶來持久化風險。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-46805 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描可達 VPN 邊界。</li>
<li>利用漏洞鏈取得初始控制。</li>
<li>建立持續存取與後續移動路徑。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>邊界設備長期暴露且承載關鍵流量。</li>
<li>修補後狀態驗證流程不足。</li>
<li>清除與重建步驟缺少標準化程序。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「修補後完整驗證」步驟，系統可能在已修補狀態下仍保留可利用持久化痕跡。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：高風險邊界設備準備替代路徑。</li>
<li>日常：建立邊界設備健康與變更基線。</li>
<li>事故中：執行隔離、重建、憑證輪替三段流程。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.ivanti.com/blog/security-update-for-ivanti-connect-secure-and-ivanti-policy-secure-gateways">ivanti.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa24-060b">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-46805">nvd.nist.gov/CVE-2023-46805</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21887">nvd.nist.gov/CVE-2024-21887</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.3 Citrix Bleed 2023：會話被劫持與重放風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-bleed-2023-session-hijack/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 Citrix Bleed（CVE-2023-4966）事件顯示，邊界設備漏洞可導致會話資訊外洩，後續引發重放與存取風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>利用邊界漏洞取得會話資料。&lt;/li>
&lt;li>重放或接管有效會話。&lt;/li>
&lt;li>以合法會話進入內部資源。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>會話機制缺少快速失效策略。&lt;/li>
&lt;li>邊界事件後憑證與會話輪替未即時執行。&lt;/li>
&lt;li>會話異常偵測與告警關聯不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「事件後全域 session/token 失效」步驟，攻擊者可在修補後持續使用已竊取會話。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：定義全域 session 失效與重發機制。&lt;/li>
&lt;li>日常：監控異常地理位置與設備指紋切換。&lt;/li>
&lt;li>事故中：修補、全域失效、強制重新登入同步執行。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.netscaler.com/blog/news/cve-2023-4966-critical-security-update-now-available-for-netscaler-adc-and-netscaler-gateway/">netscaler.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2023/11/07/cisa-releases-guidance-addressing-citrix-netscaler-adc-and-gateway-vulnerability-cve-2023-4966">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4966">nvd.nist.gov/CVE-2023-4966&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 Citrix Bleed（CVE-2023-4966）事件顯示，邊界設備漏洞可導致會話資訊外洩，後續引發重放與存取風險。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>利用邊界漏洞取得會話資料。</li>
<li>重放或接管有效會話。</li>
<li>以合法會話進入內部資源。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>會話機制缺少快速失效策略。</li>
<li>邊界事件後憑證與會話輪替未即時執行。</li>
<li>會話異常偵測與告警關聯不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「事件後全域 session/token 失效」步驟，攻擊者可在修補後持續使用已竊取會話。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：定義全域 session 失效與重發機制。</li>
<li>日常：監控異常地理位置與設備指紋切換。</li>
<li>事故中：修補、全域失效、強制重新登入同步執行。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.netscaler.com/blog/news/cve-2023-4966-critical-security-update-now-available-for-netscaler-adc-and-netscaler-gateway/">netscaler.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2023/11/07/cisa-releases-guidance-addressing-citrix-netscaler-adc-and-gateway-vulnerability-cve-2023-4966">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4966">nvd.nist.gov/CVE-2023-4966</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.4 PAN-OS 2024：邊界設備遠端命令執行</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/panos-cve-2024-3400-edge-rce/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2024 年 PAN-OS CVE-2024-3400 事件屬邊界設備高風險漏洞，對暴露在外的設備形成快速入侵壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描外網可達設備。&lt;/li>
&lt;li>觸發遠端執行能力。&lt;/li>
&lt;li>擴展到管理平面與內部網路路徑。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>邊界設備暴露面高且集中。&lt;/li>
&lt;li>修補窗口內缺少暫時緩解與替代路徑。&lt;/li>
&lt;li>攻擊偵測依賴單一訊號來源。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「修補前臨時緩解策略」，團隊會在可利用窗口內暴露完整攻擊面。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：關鍵邊界設備建立降級與備援計畫。&lt;/li>
&lt;li>日常：維護高風險資產清單與修補時限。&lt;/li>
&lt;li>事故中：先套用緩解、再分區修補與驗證。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://securityadvisories.paloaltonetworks.com/CVE-2024-3400">securityadvisories.paloaltonetworks.com/CVE-2024-3400&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-3400">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3400">nvd.nist.gov/CVE-2024-3400&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2024 年 PAN-OS CVE-2024-3400 事件屬邊界設備高風險漏洞，對暴露在外的設備形成快速入侵壓力。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描外網可達設備。</li>
<li>觸發遠端執行能力。</li>
<li>擴展到管理平面與內部網路路徑。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>邊界設備暴露面高且集中。</li>
<li>修補窗口內缺少暫時緩解與替代路徑。</li>
<li>攻擊偵測依賴單一訊號來源。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「修補前臨時緩解策略」，團隊會在可利用窗口內暴露完整攻擊面。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：關鍵邊界設備建立降級與備援計畫。</li>
<li>日常：維護高風險資產清單與修補時限。</li>
<li>事故中：先套用緩解、再分區修補與驗證。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://securityadvisories.paloaltonetworks.com/CVE-2024-3400">securityadvisories.paloaltonetworks.com/CVE-2024-3400</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-3400">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3400">nvd.nist.gov/CVE-2024-3400</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.5 PaperCut 2023：認證繞過與入口執行風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/papercut-cve-2023-27350-auth-bypass-rce/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/papercut-cve-2023-27350-auth-bypass-rce/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>PaperCut CVE-2023-27350 事件揭露管理入口的認證繞過會直接導向遠端執行與內網擴散風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描可達的 PaperCut 管理入口。&lt;/li>
&lt;li>利用認證繞過漏洞取得管理能力。&lt;/li>
&lt;li>透過服務節點進一步橫向探索。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理入口暴露面與網段隔離不足。&lt;/li>
&lt;li>入口異常行為檢測與告警門檻偏寬。&lt;/li>
&lt;li>修補後驗證與重建節奏不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「入口事件立即隔離」步驟，攻擊者可在修補前後窗口持續控制管理面。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：將管理面限制於受控網段與跳板。&lt;/li>
&lt;li>日常：為管理介面建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert&lt;/a>。&lt;/li>
&lt;li>事故中：隔離節點、完成修補、執行重測。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.papercut.com/kb/Main/PO-1216-and-PO-1219">papercut.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-27350">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-27350">nvd.nist.gov/CVE-2023-27350&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>PaperCut CVE-2023-27350 事件揭露管理入口的認證繞過會直接導向遠端執行與內網擴散風險。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描可達的 PaperCut 管理入口。</li>
<li>利用認證繞過漏洞取得管理能力。</li>
<li>透過服務節點進一步橫向探索。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理入口暴露面與網段隔離不足。</li>
<li>入口異常行為檢測與告警門檻偏寬。</li>
<li>修補後驗證與重建節奏不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「入口事件立即隔離」步驟，攻擊者可在修補前後窗口持續控制管理面。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：將管理面限制於受控網段與跳板。</li>
<li>日常：為管理介面建立 <a href="/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">symptom-based alert</a>。</li>
<li>事故中：隔離節點、完成修補、執行重測。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.papercut.com/kb/Main/PO-1216-and-PO-1219">papercut.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-27350">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-27350">nvd.nist.gov/CVE-2023-27350</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.6 Confluence 2022：網站入口 RCE 與知識系統風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-cve-2022-26134-ognl-rce/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-cve-2022-26134-ognl-rce/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Confluence CVE-2022-26134 事件顯示外網協作平台漏洞可快速形成遠端執行與資料線索外露風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描對外 Confluence 入口。&lt;/li>
&lt;li>利用 OGNL 注入取得命令執行。&lt;/li>
&lt;li>搜索內部文件與憑證線索以擴展攻擊。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>協作平台對外暴露面控制不足。&lt;/li>
&lt;li>入口服務修補與緩解同步節奏不足。&lt;/li>
&lt;li>平台資產分級與存取稽核覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補前立即緩解」步驟，攻擊者可在公告到修補完成之間持續利用。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把協作平台管理面與公開面分離。&lt;/li>
&lt;li>日常：維護高風險對外服務清單與修補 SLA。&lt;/li>
&lt;li>事故中：先緩解入口，再做修補、密碼收斂與證據保全。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://support.atlassian.com/atlassian-knowledge-base/kb/faq-for-cve-2022-26134/">support.atlassian.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2022/06/03/atlassian-releases-new-versions-confluence-server-and-data-center">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2022-26134">nvd.nist.gov/CVE-2022-26134&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Confluence CVE-2022-26134 事件顯示外網協作平台漏洞可快速形成遠端執行與資料線索外露風險。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描對外 Confluence 入口。</li>
<li>利用 OGNL 注入取得命令執行。</li>
<li>搜索內部文件與憑證線索以擴展攻擊。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>協作平台對外暴露面控制不足。</li>
<li>入口服務修補與緩解同步節奏不足。</li>
<li>平台資產分級與存取稽核覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補前立即緩解」步驟，攻擊者可在公告到修補完成之間持續利用。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把協作平台管理面與公開面分離。</li>
<li>日常：維護高風險對外服務清單與修補 SLA。</li>
<li>事故中：先緩解入口，再做修補、密碼收斂與證據保全。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://support.atlassian.com/atlassian-knowledge-base/kb/faq-for-cve-2022-26134/">support.atlassian.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2022/06/03/atlassian-releases-new-versions-confluence-server-and-data-center">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2022-26134">nvd.nist.gov/CVE-2022-26134</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.7 Cisco IOS XE 2023：Web UI 管理面風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/cisco-ios-xe-cve-2023-20198-webui-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/cisco-ios-xe-cve-2023-20198-webui-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Cisco IOS XE Web UI CVE-2023-20198 事件凸顯網路設備管理面一旦暴露，攻擊可快速取得高權限控制能力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描並辨識可達 Web UI 管理入口。&lt;/li>
&lt;li>利用漏洞建立未授權存取。&lt;/li>
&lt;li>取得設備控制能力並擴展網路影響。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理介面暴露於不必要的外網範圍。&lt;/li>
&lt;li>設備硬化與版本治理節奏不足。&lt;/li>
&lt;li>異常管理操作與配置變更稽核不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「管理平面異常即鎖定」步驟，設備控制權會長時間留在攻擊者手中。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把管理面封裝於專用管理網段。&lt;/li>
&lt;li>日常：定期稽核設定變更與登入來源。&lt;/li>
&lt;li>事故中：隔離設備、重建設定、驗證路由完整性。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-webui-privesc-j22SaA4z">sec.cloudapps.cisco.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2023/10/16/cisco-releases-security-advisory-ios-xe-software-web-ui">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-20198">nvd.nist.gov/CVE-2023-20198&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Cisco IOS XE Web UI CVE-2023-20198 事件凸顯網路設備管理面一旦暴露，攻擊可快速取得高權限控制能力。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描並辨識可達 Web UI 管理入口。</li>
<li>利用漏洞建立未授權存取。</li>
<li>取得設備控制能力並擴展網路影響。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理介面暴露於不必要的外網範圍。</li>
<li>設備硬化與版本治理節奏不足。</li>
<li>異常管理操作與配置變更稽核不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「管理平面異常即鎖定」步驟，設備控制權會長時間留在攻擊者手中。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把管理面封裝於專用管理網段。</li>
<li>日常：定期稽核設定變更與登入來源。</li>
<li>事故中：隔離設備、重建設定、驗證路由完整性。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-webui-privesc-j22SaA4z">sec.cloudapps.cisco.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2023/10/16/cisco-releases-security-advisory-ios-xe-software-web-ui">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-20198">nvd.nist.gov/CVE-2023-20198</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.8 Fortinet SSL-VPN 2024：邊界 VPN 高風險窗口</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-ssl-vpn-cve-2024-21762/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-ssl-vpn-cve-2024-21762/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Fortinet CVE-2024-21762 事件顯示 VPN 邊界漏洞在實戰環境中具備快速利用壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描暴露的 SSL-VPN 入口。&lt;/li>
&lt;li>利用漏洞取得未授權執行能力。&lt;/li>
&lt;li>以邊界設備作為內網進入點。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>邊界 VPN 集中承載遠端存取流量。&lt;/li>
&lt;li>高風險漏洞公告後隔離節奏不足。&lt;/li>
&lt;li>修補完成後健康檢查覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補前臨時緩解」步驟，攻擊者可在短時間窗內持續命中邊界設備。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：準備 VPN 流量切換與替代通道。&lt;/li>
&lt;li>日常：維護高風險設備資產清單與補丁時限。&lt;/li>
&lt;li>事故中：先隔離入口，再進行分區修補與復測。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://fortiguard.com/psirt/FG-IR-24-015">fortiguard.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>資安廠商深度分析、IoC、利用樣態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-21762">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21762">nvd.nist.gov/CVE-2024-21762&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Fortinet CVE-2024-21762 事件顯示 VPN 邊界漏洞在實戰環境中具備快速利用壓力。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描暴露的 SSL-VPN 入口。</li>
<li>利用漏洞取得未授權執行能力。</li>
<li>以邊界設備作為內網進入點。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>邊界 VPN 集中承載遠端存取流量。</li>
<li>高風險漏洞公告後隔離節奏不足。</li>
<li>修補完成後健康檢查覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補前臨時緩解」步驟，攻擊者可在短時間窗內持續命中邊界設備。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：準備 VPN 流量切換與替代通道。</li>
<li>日常：維護高風險設備資產清單與補丁時限。</li>
<li>事故中：先隔離入口，再進行分區修補與復測。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://fortiguard.com/psirt/FG-IR-24-015">fortiguard.com</a></td>
          <td>技術分析</td>
          <td>資安廠商深度分析、IoC、利用樣態</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-21762">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-21762">nvd.nist.gov/CVE-2024-21762</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.9 SysAid 2023：ITSM 入口與維運流程風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/sysaid-cve-2023-47246-itsm-entrypoint/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/sysaid-cve-2023-47246-itsm-entrypoint/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>SysAid CVE-2023-47246 事件指出 ITSM 平台入口漏洞可直接影響維運管理與企業內部流程。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描 ITSM 對外服務入口。&lt;/li>
&lt;li>利用漏洞取得管理層存取。&lt;/li>
&lt;li>接觸工單、資產或維運權限資訊。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>ITSM 管理面缺少網段隔離。&lt;/li>
&lt;li>工單與維運權限分離策略不足。&lt;/li>
&lt;li>入口事件與維運告警未形成閉環。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「ITSM 事件時工單權限收斂」步驟，攻擊者能利用維運流程長時間維持影響力。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把 ITSM 高權限動作拆成雙人核准。&lt;/li>
&lt;li>日常：追蹤異常管理操作與高風險工單。&lt;/li>
&lt;li>事故中：停用可疑管理帳號並重建權限。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.sysaid.com/blog/service-desk/on-premise-software-security-vulnerability-notification">sysaid.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-47246">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-47246">nvd.nist.gov/CVE-2023-47246&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>SysAid CVE-2023-47246 事件指出 ITSM 平台入口漏洞可直接影響維運管理與企業內部流程。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描 ITSM 對外服務入口。</li>
<li>利用漏洞取得管理層存取。</li>
<li>接觸工單、資產或維運權限資訊。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>ITSM 管理面缺少網段隔離。</li>
<li>工單與維運權限分離策略不足。</li>
<li>入口事件與維運告警未形成閉環。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「ITSM 事件時工單權限收斂」步驟，攻擊者能利用維運流程長時間維持影響力。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把 ITSM 高權限動作拆成雙人核准。</li>
<li>日常：追蹤異常管理操作與高風險工單。</li>
<li>事故中：停用可疑管理帳號並重建權限。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.sysaid.com/blog/service-desk/on-premise-software-security-vulnerability-notification">sysaid.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-47246">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-47246">nvd.nist.gov/CVE-2023-47246</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.1 LastPass 2022：備份路徑與鏈式入侵</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/lastpass-2022-backup-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2022 年 LastPass 多次公告顯示，事件由開發環境路徑延伸到雲端備份資料存取，形成鏈式資料風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：開發環境 → 備份系統 → 加密保管庫的鏈式擴散，重點在「備份層 vs 正式環境層」的權限 / 金鑰隔離。其他 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>在上游環境取得關鍵資訊。&lt;/li>
&lt;li>使用關聯資訊打開備份存取路徑。&lt;/li>
&lt;li>造成長尾資料保護壓力。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>備份資產分級與隔離不足。&lt;/li>
&lt;li>金鑰管理與資料路徑治理耦合過高。&lt;/li>
&lt;li>備份讀取異常告警覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「備份層獨立權限審核」，事件即使起點在開發層，也能快速擴張到高敏感資料。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：備份與正式環境使用不同權限域（不同 IAM principal、不同 KMS key audience），mechanism 是讓正式環境的接管不直接通到備份。&lt;/li>
&lt;li>日常：定期審查備份讀取行為與授權範圍（哪些 principal 在哪些時段讀備份的 audit trail）。&lt;/li>
&lt;li>事故中：啟動備份層獨立調查與金鑰輪替（前提是備份金鑰跟正式金鑰是分離 lifecycle）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> —— 把樣式轉成 tabletop、credential 治理與備份回復欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend/01-database&lt;/a> 的備份與恢復設計。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於 post-compromise 鏈式擴散、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://blog.lastpass.com/2022/08/notice-of-recent-security-incident/">blog.lastpass.com&lt;/a>&lt;/td>
 &lt;td>官方初報&lt;/td>
 &lt;td>開發環境入口、初步影響評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://blog.lastpass.com/2022/11/notice-of-recent-security-incident/">blog.lastpass.com&lt;/a>&lt;/td>
 &lt;td>官方延伸&lt;/td>
 &lt;td>第二階段揭露、雲端備份存取&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://blog.lastpass.com/2022/12/notice-of-recent-security-incident/">blog.lastpass.com&lt;/a>&lt;/td>
 &lt;td>官方終報&lt;/td>
 &lt;td>完整影響範圍、客戶行動建議&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2022 年 LastPass 多次公告顯示，事件由開發環境路徑延伸到雲端備份資料存取，形成鏈式資料風險。</p>
<p><strong>本案例的演示焦點</strong>：開發環境 → 備份系統 → 加密保管庫的鏈式擴散，重點在「備份層 vs 正式環境層」的權限 / 金鑰隔離。其他 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>在上游環境取得關鍵資訊。</li>
<li>使用關聯資訊打開備份存取路徑。</li>
<li>造成長尾資料保護壓力。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>備份資產分級與隔離不足。</li>
<li>金鑰管理與資料路徑治理耦合過高。</li>
<li>備份讀取異常告警覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「備份層獨立權限審核」，事件即使起點在開發層，也能快速擴張到高敏感資料。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：備份與正式環境使用不同權限域（不同 IAM principal、不同 KMS key audience），mechanism 是讓正式環境的接管不直接通到備份。</li>
<li>日常：定期審查備份讀取行為與授權範圍（哪些 principal 在哪些時段讀備份的 audit trail）。</li>
<li>事故中：啟動備份層獨立調查與金鑰輪替（前提是備份金鑰跟正式金鑰是分離 lifecycle）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/secrets-and-machine-credential-governance/" data-link-title="7.6 秘密管理與機器憑證治理" data-link-desc="以問題驅動方式整理 secret、token、key 與機器身份治理">7.8 secrets 與機器憑證治理</a> + <a href="/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理</a> + <a href="/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> —— 把樣式轉成 tabletop、credential 治理與備份回復欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend/01-database</a> 的備份與恢復設計。</li>
</ul>
<p>本案例屬於 post-compromise 鏈式擴散、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://blog.lastpass.com/2022/08/notice-of-recent-security-incident/">blog.lastpass.com</a></td>
          <td>官方初報</td>
          <td>開發環境入口、初步影響評估</td>
      </tr>
      <tr>
          <td><a href="https://blog.lastpass.com/2022/11/notice-of-recent-security-incident/">blog.lastpass.com</a></td>
          <td>官方延伸</td>
          <td>第二階段揭露、雲端備份存取</td>
      </tr>
      <tr>
          <td><a href="https://blog.lastpass.com/2022/12/notice-of-recent-security-incident/">blog.lastpass.com</a></td>
          <td>官方終報</td>
          <td>完整影響範圍、客戶行動建議</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.2 Snowflake 2024：憑證濫用與資料竊取</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/snowflake-2024-credential-abuse/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2024 年公開資訊指出，攻擊者利用外洩憑證在部分 Snowflake 客戶環境進行資料竊取與勒索活動。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：infostealer 收集的憑證 → MFA / network policy 缺口 → 大量查詢 / 匯出的資料外送 chain。重點在「資料平台 access policy + 異常匯出偵測」設計、其他 threat surface 由其他 case category 承擔。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>收集可用憑證。&lt;/li>
&lt;li>針對 MFA 或存取政策薄弱環境登入。&lt;/li>
&lt;li>執行大量查詢與資料外送。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>身分基線未強制 MFA 與條件式存取。&lt;/li>
&lt;li>查詢行為異常偵測門檻不足。&lt;/li>
&lt;li>高價值資料匯出控制較弱。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「憑證事件後立即收斂存取政策」，攻擊者可在低噪音情況下持續外送資料。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：資料平台預設強制 MFA 與網路政策（network rule allowlist / 條件式存取），mechanism 是讓 leaked credential 即使有效也碰不到資料平台。&lt;/li>
&lt;li>日常：建立異常查詢與匯出告警（query 體積 / 來源 IP / 跨 schema scan 模式）。&lt;/li>
&lt;li>事故中：分批停用可疑憑證、限制外送並啟動調查（前提是事先有 credential inventory + 分批撤銷能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/bulk-operation-abuse/" data-link-title="7.R11.10 批次操作濫用" data-link-desc="說明批次操作為何容易放大單次權限失效的影響半徑">批次操作濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/fp-long-lived-repeatable-export-artifact/" data-link-title="7.R11.P8 匯出檔案長時間可重複下載" data-link-desc="說明匯出產物長時效與可重複下載如何放大資料外送風險">Long-lived repeatable export artifact&lt;/a> —— 把 leaked credential → bulk export 的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> —— 把樣式轉成 tabletop 與 release gate 欄位。&lt;/li>
&lt;/ul>
&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;a href="https://www.snowflake.com/en/blog/communication-on-recent-cyber-threat-activity/">snowflake.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊入口、影響範圍、客戶側建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2024/06/03/snowflake-recommends-customers-take-steps-prevent-unauthorized-access">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC5537 TTP、infostealer 來源、勒索鏈 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2024 年公開資訊指出，攻擊者利用外洩憑證在部分 Snowflake 客戶環境進行資料竊取與勒索活動。</p>
<p><strong>本案例的演示焦點</strong>：infostealer 收集的憑證 → MFA / network policy 缺口 → 大量查詢 / 匯出的資料外送 chain。重點在「資料平台 access policy + 異常匯出偵測」設計、其他 threat surface 由其他 case category 承擔。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>收集可用憑證。</li>
<li>針對 MFA 或存取政策薄弱環境登入。</li>
<li>執行大量查詢與資料外送。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>身分基線未強制 MFA 與條件式存取。</li>
<li>查詢行為異常偵測門檻不足。</li>
<li>高價值資料匯出控制較弱。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「憑證事件後立即收斂存取政策」，攻擊者可在低噪音情況下持續外送資料。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：資料平台預設強制 MFA 與網路政策（network rule allowlist / 條件式存取），mechanism 是讓 leaked credential 即使有效也碰不到資料平台。</li>
<li>日常：建立異常查詢與匯出告警（query 體積 / 來源 IP / 跨 schema scan 模式）。</li>
<li>事故中：分批停用可疑憑證、限制外送並啟動調查（前提是事先有 credential inventory + 分批撤銷能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/bulk-operation-abuse/" data-link-title="7.R11.10 批次操作濫用" data-link-desc="說明批次操作為何容易放大單次權限失效的影響半徑">批次操作濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/fp-long-lived-repeatable-export-artifact/" data-link-title="7.R11.P8 匯出檔案長時間可重複下載" data-link-desc="說明匯出產物長時效與可重複下載如何放大資料外送風險">Long-lived repeatable export artifact</a> —— 把 leaked credential → bulk export 的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> —— 把樣式轉成 tabletop 與 release gate 欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.snowflake.com/en/blog/communication-on-recent-cyber-threat-activity/">snowflake.com</a></td>
          <td>官方</td>
          <td>攻擊入口、影響範圍、客戶側建議</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2024/06/03/snowflake-recommends-customers-take-steps-prevent-unauthorized-access">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC5537 TTP、infostealer 來源、勒索鏈 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2024 年 Change Healthcare 事件顯示，資安事件可同時造成資料風險與支付流程中斷，影響範圍跨越供應鏈與醫療營運。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：高集中度業務中樞被勒索 → 下游機構 / 現金流連鎖中斷的 data-incident-to-business-continuity 事件。重點在「資安處置」跟「業務連續性處置」分軌並行的 workflow 設計。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊核心服務入口。&lt;/li>
&lt;li>影響高集中度業務中樞。&lt;/li>
&lt;li>對下游機構與現金流造成連鎖效應。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>關鍵業務中樞集中度高。&lt;/li>
&lt;li>替代流程與手動回復路徑準備不足。&lt;/li>
&lt;li>安全事件與業務連續性計畫連結不夠緊密。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「事故中的業務連續性切換流程」，團隊會在技術修復之外承受長期營運中斷代價。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：定義核心流程的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO&lt;/a>，mechanism 是讓「資料修復時間」跟「業務可接受中斷時間」明示對照、不藏在直覺。&lt;/li>
&lt;li>日常：演練核心交易路徑的降級方案（含手動 fallback / 替代供應商接手）。&lt;/li>
&lt;li>事故中：技術處置與業務處置分軌並行（前提是事先有 dual-track IC 角色、不臨時拉人）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成 tabletop、回復演練與證據欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability&lt;/a> 的可用性與備援設計、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的事故分級與跨部門通訊。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於 post-compromise 影響類別、不對應紅隊 problem-cards（後者集中於 access flow 失效），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.unitedhealthgroup.com/newsroom/2024/2024-04-22-uhg-updates-on-change-healthcare-cyberattack.html">unitedhealthgroup.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊時序、影響範圍、復原節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cms.gov/newsroom/press-releases/cms-statement-change-healthcare-cyberattack">cms.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>監管面回應、對下游醫療機構的影響評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.aha.org/cybersecurity/change-healthcare-cyberattack-updates">aha.org&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>醫療業界 ongoing impact tracking、業務連續性影響&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2024 年 Change Healthcare 事件顯示，資安事件可同時造成資料風險與支付流程中斷，影響範圍跨越供應鏈與醫療營運。</p>
<p><strong>本案例的演示焦點</strong>：高集中度業務中樞被勒索 → 下游機構 / 現金流連鎖中斷的 data-incident-to-business-continuity 事件。重點在「資安處置」跟「業務連續性處置」分軌並行的 workflow 設計。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊核心服務入口。</li>
<li>影響高集中度業務中樞。</li>
<li>對下游機構與現金流造成連鎖效應。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>關鍵業務中樞集中度高。</li>
<li>替代流程與手動回復路徑準備不足。</li>
<li>安全事件與業務連續性計畫連結不夠緊密。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「事故中的業務連續性切換流程」，團隊會在技術修復之外承受長期營運中斷代價。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：定義核心流程的 <a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> 與 <a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a>，mechanism 是讓「資料修復時間」跟「業務可接受中斷時間」明示對照、不藏在直覺。</li>
<li>日常：演練核心交易路徑的降級方案（含手動 fallback / 替代供應商接手）。</li>
<li>事故中：技術處置與業務處置分軌並行（前提是事先有 dual-track IC 角色、不臨時拉人）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈</a> + <a href="/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成 tabletop、回復演練與證據欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability</a> 的可用性與備援設計、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的事故分級與跨部門通訊。</li>
</ul>
<p>本案例屬於 post-compromise 影響類別、不對應紅隊 problem-cards（後者集中於 access flow 失效），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.unitedhealthgroup.com/newsroom/2024/2024-04-22-uhg-updates-on-change-healthcare-cyberattack.html">unitedhealthgroup.com</a></td>
          <td>官方</td>
          <td>攻擊時序、影響範圍、復原節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cms.gov/newsroom/press-releases/cms-statement-change-healthcare-cyberattack">cms.gov</a></td>
          <td>政府/監管</td>
          <td>監管面回應、對下游醫療機構的影響評估</td>
      </tr>
      <tr>
          <td><a href="https://www.aha.org/cybersecurity/change-healthcare-cyberattack-updates">aha.org</a></td>
          <td>技術分析</td>
          <td>醫療業界 ongoing impact tracking、業務連續性影響</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.4 Mailchimp 2023：支援工具路徑與客戶資料風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/mailchimp-2023-support-tool-abuse/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/mailchimp-2023-support-tool-abuse/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>2023 年 1 月，Mailchimp 公告指出攻擊者透過社交工程取得員工憑證，接觸客服/帳號管理工具並影響特定客戶帳號。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：員工社交工程 → 客服 / 帳號管理工具接管 → 客戶資料 read / 變更的 internal admin tool exfiltration。重點在「合法 admin 動作」跟「攻擊樣態」的偵測差異設計。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊員工身份。&lt;/li>
&lt;li>進入客服與帳號管理工具。&lt;/li>
&lt;li>存取或操作特定客戶資訊。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>客服工具高權限操作缺少額外防線。&lt;/li>
&lt;li>角色分離與操作稽核不夠完整。&lt;/li>
&lt;li>社交工程應對流程不夠制度化。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若缺少「高風險客服操作二次驗證」，攻擊者使用合法員工身份即可直接接觸高敏感客戶資產。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：對客服工具高風險操作加上雙人核准（access customer data / impersonate / 大批量 export 三類動作必須 multi-party），mechanism 是讓單一帳號接管不會直接通到客戶資料。&lt;/li>
&lt;li>日常：追蹤管理工具異常操作模式（單一 operator 短時間跨多 tenant、異常時段 access）。&lt;/li>
&lt;li>事故中：快速凍結可疑角色與工單操作權限（前提是事先有 role-level kill switch）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>失效樣式&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用&lt;/a> —— 把員工身分 → 客服工具 → 客戶資料的 mechanism 抽象為可重用失效樣式。&lt;/li>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 稽核軌跡與責任邊界&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern&lt;/a> —— 把樣式轉成 tabletop 與 admin tool 治理欄位。&lt;/li>
&lt;/ul>
&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;a href="https://mailchimp.com/newsroom/january-2023-security-incident/">mailchimp.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>攻擊入口、影響範圍、客戶通報節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>跨組織 social engineering TTP&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>UNC3944 對 SaaS / admin tool 攻擊模式 telemetry&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>2023 年 1 月，Mailchimp 公告指出攻擊者透過社交工程取得員工憑證，接觸客服/帳號管理工具並影響特定客戶帳號。</p>
<p><strong>本案例的演示焦點</strong>：員工社交工程 → 客服 / 帳號管理工具接管 → 客戶資料 read / 變更的 internal admin tool exfiltration。重點在「合法 admin 動作」跟「攻擊樣態」的偵測差異設計。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊員工身份。</li>
<li>進入客服與帳號管理工具。</li>
<li>存取或操作特定客戶資訊。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>客服工具高權限操作缺少額外防線。</li>
<li>角色分離與操作稽核不夠完整。</li>
<li>社交工程應對流程不夠制度化。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若缺少「高風險客服操作二次驗證」，攻擊者使用合法員工身份即可直接接觸高敏感客戶資產。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：對客服工具高風險操作加上雙人核准（access customer data / impersonate / 大批量 export 三類動作必須 multi-party），mechanism 是讓單一帳號接管不會直接通到客戶資料。</li>
<li>日常：追蹤管理工具異常操作模式（單一 operator 短時間跨多 tenant、異常時段 access）。</li>
<li>事故中：快速凍結可疑角色與工單操作權限（前提是事先有 role-level kill switch）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>失效樣式</strong>：<a href="/blog/backend/07-security-data-protection/red-team/problem-cards/privilege-escalation-flow-abuse/" data-link-title="7.R11.6 權限提升流程濫用" data-link-desc="說明權限提升流程為何容易把局部存取轉成全域控制">權限提升流程濫用</a> + <a href="/blog/backend/07-security-data-protection/red-team/problem-cards/delegated-operation-abuse/" data-link-title="7.R11.3 代理操作濫用" data-link-desc="說明代理操作為何容易形成責任鏈斷點與高權限濫用">委派操作濫用</a> —— 把員工身分 → 客服工具 → 客戶資料的 mechanism 抽象為可重用失效樣式。</li>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/identity-access-boundary/" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> + <a href="/blog/backend/07-security-data-protection/audit-trail-and-accountability-boundary/" data-link-title="7.7 稽核追蹤與責任邊界" data-link-desc="以問題驅動方式整理高風險操作追蹤、可回查與責任切分">7.7 稽核軌跡與責任邊界</a> + <a href="/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/identity-support-token-tabletop/" data-link-title="Identity Support Token Tabletop" data-link-desc="以支援流程與 session token 風險設計身份接管 tabletop 情境">Identity support token tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/credential-hygiene-pattern/" data-link-title="Credential Hygiene Pattern" data-link-desc="定義 credential、MFA、輪替、infostealer 監控與 network boundary 的共同基線">Credential hygiene pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/control-owner-pattern/" data-link-title="Control Owner Pattern" data-link-desc="定義高風險控制面如何配置 owner、協作角色、決策角色與升級路徑">Control owner pattern</a> —— 把樣式轉成 tabletop 與 admin tool 治理欄位。</li>
</ul>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://mailchimp.com/newsroom/january-2023-security-incident/">mailchimp.com</a></td>
          <td>官方</td>
          <td>攻擊入口、影響範圍、客戶通報節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>跨組織 social engineering TTP</td>
      </tr>
      <tr>
          <td><a href="https://cloud.google.com/blog/topics/threat-intelligence/unc3944-targets-saas-applications">cloud.google.com</a></td>
          <td>技術分析</td>
          <td>UNC3944 對 SaaS / admin tool 攻擊模式 telemetry</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.5 VMware ESXiArgs 2023：虛擬化平台勒索回復壓力</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>ESXiArgs 事件顯示 CVE-2021-21974 與 CVE-2021-21972 這類虛擬化平台漏洞可轉為大規模勒索與服務中斷，回復節奏成為關鍵控制面。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：虛擬化平台舊漏洞（patch-available 但未套用）→ ESXi host 加密 → 大量 VM 同時不可用的 mass-ransom 事件。重點在「回復節奏 vs 業務優先級」設計、exfiltration 本身是次要面向。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>利用已知 ESXi 漏洞取得主機控制能力。&lt;/li>
&lt;li>執行加密或破壞作業影響虛擬機。&lt;/li>
&lt;li>造成資料可用性與業務連續性衝擊。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>虛擬化平台修補節奏與資產可見性不足。&lt;/li>
&lt;li>快照、備份與復原演練覆蓋不足。&lt;/li>
&lt;li>事故中回復優先級路由不夠明確。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「回復優先級排序」步驟，團隊會在高壓情境下延長核心服務停擺時間。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：定義核心服務的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO&lt;/a>（依業務重要性分層、不平均對待），mechanism 是讓事件期間的回復排序有預先決定的依據。&lt;/li>
&lt;li>日常：演練備份還原並記錄 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR&lt;/a>（含「整個 hypervisor fleet 同時離線」的壓力測試）。&lt;/li>
&lt;li>事故中：先恢復核心服務、再分批回補次要工作負載（前提是備份跟受影響 hypervisor 是 air-gap、不會同步加密）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成回復演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability&lt;/a> 的備援與回復策略、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的回復決策流程。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於 mass-ransom 事件、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.vmware.com/security/advisories/VMSA-2021-0002.html">vmware.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、修補節奏、緩解步驟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-040a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>大規模 ESXiArgs campaign 處置建議、recovery 工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21972">nvd.nist.gov/CVE-2021-21972&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE-2021-21972 細節、unauthenticated RCE 機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21974">nvd.nist.gov/CVE-2021-21974&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE-2021-21974 細節、SLP heap overflow 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>ESXiArgs 事件顯示 CVE-2021-21974 與 CVE-2021-21972 這類虛擬化平台漏洞可轉為大規模勒索與服務中斷，回復節奏成為關鍵控制面。</p>
<p><strong>本案例的演示焦點</strong>：虛擬化平台舊漏洞（patch-available 但未套用）→ ESXi host 加密 → 大量 VM 同時不可用的 mass-ransom 事件。重點在「回復節奏 vs 業務優先級」設計、exfiltration 本身是次要面向。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>利用已知 ESXi 漏洞取得主機控制能力。</li>
<li>執行加密或破壞作業影響虛擬機。</li>
<li>造成資料可用性與業務連續性衝擊。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>虛擬化平台修補節奏與資產可見性不足。</li>
<li>快照、備份與復原演練覆蓋不足。</li>
<li>事故中回復優先級路由不夠明確。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「回復優先級排序」步驟，團隊會在高壓情境下延長核心服務停擺時間。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：定義核心服務的 <a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> 與 <a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a>（依業務重要性分層、不平均對待），mechanism 是讓事件期間的回復排序有預先決定的依據。</li>
<li>日常：演練備份還原並記錄 <a href="/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR</a>（含「整個 hypervisor fleet 同時離線」的壓力測試）。</li>
<li>事故中：先恢復核心服務、再分批回補次要工作負載（前提是備份跟受影響 hypervisor 是 air-gap、不會同步加密）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈</a> + <a href="/blog/backend/07-security-data-protection/security-routing-from-case-to-service/" data-link-title="7.8 模組路由：問題到服務實作" data-link-desc="整理問題節點如何路由到部署、可靠性與事故處理章節">7.13 安全事件路由</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/recovery-readiness-pattern/" data-link-title="Recovery Readiness Pattern" data-link-desc="定義長時間 outage 復原、備援存取與外部依賴溝通的共同欄位">Recovery readiness pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成回復演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend/06-reliability</a> 的備援與回復策略、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的回復決策流程。</li>
</ul>
<p>本案例屬於 mass-ransom 事件、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.vmware.com/security/advisories/VMSA-2021-0002.html">vmware.com</a></td>
          <td>官方</td>
          <td>受影響版本、修補節奏、緩解步驟</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-040a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>大規模 ESXiArgs campaign 處置建議、recovery 工具</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21972">nvd.nist.gov/CVE-2021-21972</a></td>
          <td>技術分析</td>
          <td>CVE-2021-21972 細節、unauthenticated RCE 機制</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21974">nvd.nist.gov/CVE-2021-21974</a></td>
          <td>技術分析</td>
          <td>CVE-2021-21974 細節、SLP heap overflow 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>WS_FTP 2023 事件顯示對外檔案服務漏洞能快速變成資料外送事件，並帶來長尾調查壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：對外檔案服務 zero-day → 批量下載 → 資料外送的 file-server exfiltration。跟 GoAnywhere / MOVEit 共同形成 file-transfer 平台 systemic risk 視角，但 WS_FTP 屬中小企業 footprint、暴露面更分散。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描可達的 WS_FTP 服務。&lt;/li>
&lt;li>利用漏洞取得檔案存取能力。&lt;/li>
&lt;li>批量下載或外送高價值資料。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>對外檔案服務缺少最小暴露策略。&lt;/li>
&lt;li>檔案下載異常偵測覆蓋不足。&lt;/li>
&lt;li>事件時封鎖與保全流程節奏不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「異常外送即時封鎖」步驟，攻擊者可在同一窗口擴大資料外送規模。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：將檔案服務納入獨立網段與存取白名單（IP allowlist / VPN-fronted），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。&lt;/li>
&lt;li>日常：對大批量下載建立 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook&lt;/a>（單客戶 / 單 IP 短時間下載量級異常）。&lt;/li>
&lt;li>事故中：先封鎖外送路徑、再啟動調查與通知流程（前提是事先有 service-level cut-off 開關）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> —— 把樣式轉成 tabletop 與漏洞處理欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的通報與追蹤。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 zero-day 引發的外送、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.progress.com/trust-center/security-advisory/ws_ftp-server">progress.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-40044">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-40044">nvd.nist.gov/CVE-2023-40044&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、deserialization 利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>WS_FTP 2023 事件顯示對外檔案服務漏洞能快速變成資料外送事件，並帶來長尾調查壓力。</p>
<p><strong>本案例的演示焦點</strong>：對外檔案服務 zero-day → 批量下載 → 資料外送的 file-server exfiltration。跟 GoAnywhere / MOVEit 共同形成 file-transfer 平台 systemic risk 視角，但 WS_FTP 屬中小企業 footprint、暴露面更分散。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描可達的 WS_FTP 服務。</li>
<li>利用漏洞取得檔案存取能力。</li>
<li>批量下載或外送高價值資料。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>對外檔案服務缺少最小暴露策略。</li>
<li>檔案下載異常偵測覆蓋不足。</li>
<li>事件時封鎖與保全流程節奏不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「異常外送即時封鎖」步驟，攻擊者可在同一窗口擴大資料外送規模。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：將檔案服務納入獨立網段與存取白名單（IP allowlist / VPN-fronted），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。</li>
<li>日常：對大批量下載建立 <a href="/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook</a>（單客戶 / 單 IP 短時間下載量級異常）。</li>
<li>事故中：先封鎖外送路徑、再啟動調查與通知流程（前提是事先有 service-level cut-off 開關）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> —— 把樣式轉成 tabletop 與漏洞處理欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的通報與追蹤。</li>
</ul>
<p>本案例屬於邊界 zero-day 引發的外送、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.progress.com/trust-center/security-advisory/ws_ftp-server">progress.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-40044">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-40044">nvd.nist.gov/CVE-2023-40044</a></td>
          <td>技術分析</td>
          <td>CVE 細節、deserialization 利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.4.7 GoAnywhere MFT 2023：傳輸中樞被利用的外送鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/goanywhere-mft-2023-exfiltration-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/goanywhere-mft-2023-exfiltration-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>GoAnywhere MFT 2023 事件顯示檔案傳輸中樞在漏洞事件中會快速演變為資料外送與供應鏈通知壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：MFT 中樞 zero-day → 跨組織交換資料批量外送 → 多客戶通報壓力的 file-transfer hub exfiltration。跟 MOVEit 同類別、共同說明 MFT 平台暴露面的 systemic risk。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定可達 MFT 入口。&lt;/li>
&lt;li>利用漏洞取得傳輸系統存取能力。&lt;/li>
&lt;li>搜集並外送跨組織交換資料。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>傳輸中樞缺少入口隔離與最小授權。&lt;/li>
&lt;li>傳輸行為與資料分級未有效關聯。&lt;/li>
&lt;li>事件中跨組織通報流程準備不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「受影響交易清單快速盤點」步驟，團隊會延後通知與修復決策，擴大業務衝擊。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：為 MFT 流程建立資料分級與權限分域（依交易對象 / 資料敏感度切 audience），mechanism 是讓單點漏洞不會通到全部交換資料。&lt;/li>
&lt;li>日常：維護交易追蹤與外送告警指標（單窗口下載量 / 跨 partner 異常 access pattern）。&lt;/li>
&lt;li>事故中：盤點受影響交易、封鎖傳輸路徑、分層通知利害關係人（前提是事先有 partner contact map）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成 tabletop、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend/01-database&lt;/a> 的資料分級與治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的跨組織通報流程。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 zero-day 引發的外送、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.fortra.com/blog/summary-investigation-related-cve-2023-0669">fortra.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、修補時序、調查結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-0669">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-0669">nvd.nist.gov/CVE-2023-0669&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、deserialization RCE 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>GoAnywhere MFT 2023 事件顯示檔案傳輸中樞在漏洞事件中會快速演變為資料外送與供應鏈通知壓力。</p>
<p><strong>本案例的演示焦點</strong>：MFT 中樞 zero-day → 跨組織交換資料批量外送 → 多客戶通報壓力的 file-transfer hub exfiltration。跟 MOVEit 同類別、共同說明 MFT 平台暴露面的 systemic risk。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定可達 MFT 入口。</li>
<li>利用漏洞取得傳輸系統存取能力。</li>
<li>搜集並外送跨組織交換資料。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>傳輸中樞缺少入口隔離與最小授權。</li>
<li>傳輸行為與資料分級未有效關聯。</li>
<li>事件中跨組織通報流程準備不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「受影響交易清單快速盤點」步驟，團隊會延後通知與修復決策，擴大業務衝擊。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：為 MFT 流程建立資料分級與權限分域（依交易對象 / 資料敏感度切 audience），mechanism 是讓單點漏洞不會通到全部交換資料。</li>
<li>日常：維護交易追蹤與外送告警指標（單窗口下載量 / 跨 partner 異常 access pattern）。</li>
<li>事故中：盤點受影響交易、封鎖傳輸路徑、分層通知利害關係人（前提是事先有 partner contact map）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/data-protection-and-masking-governance/" data-link-title="7.4 資料保護與遮罩治理" data-link-desc="以問題驅動方式整理資料分級、遮罩、匯出與備份治理">7.9 資料保護與遮罩治理</a> + <a href="/blog/backend/07-security-data-protection/data-residency-deletion-and-evidence-chain/" data-link-title="7.11 資料駐留、刪除與證據鏈" data-link-desc="定義跨區資料駐留、刪除請求與可驗證證據鏈問題">7.10 資料 residency / 刪除與證據鏈</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/low-frequency-exfiltration-tabletop/" data-link-title="Low-frequency Exfiltration Tabletop" data-link-desc="以受管檔案傳輸系統外送風險設計資料範圍與通報 tabletop">Low-frequency exfiltration tabletop</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成 tabletop、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend/01-database</a> 的資料分級與治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的跨組織通報流程。</li>
</ul>
<p>本案例屬於邊界 zero-day 引發的外送、不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.fortra.com/blog/summary-investigation-related-cve-2023-0669">fortra.com</a></td>
          <td>官方</td>
          <td>受影響版本、修補時序、調查結果</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-0669">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-0669">nvd.nist.gov/CVE-2023-0669</a></td>
          <td>技術分析</td>
          <td>CVE 細節、deserialization RCE 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.2.10 TeamCity 2024：CVE-2024-27198/27199 入口鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-2024-cve-27198-27199-auth-path-traversal/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>TeamCity 2024 事件顯示 CI 平台在認證繞過與路徑穿越漏洞同時存在時，交付鏈風險會被快速放大。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：CI 管理介面 auth bypass + path traversal 雙漏洞 → build pipeline 接管 → artifact 污染擴散下游。屬 CI-platform entrypoint 漏洞鏈、跟 SolarWinds 類 build-time 植入互補。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者鎖定可達 TeamCity 管理入口。&lt;/li>
&lt;li>利用 CVE-2024-27198 或 CVE-2024-27199 取得未授權能力。&lt;/li>
&lt;li>觸及 build 任務與 artifact 交付路徑。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>CI 管理介面隔離與存取控制不足。&lt;/li>
&lt;li>交付鏈完整性驗證節奏不足。&lt;/li>
&lt;li>事件時部署凍結與回退流程聯動不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「CI 事件即凍結交付」步驟，受影響 artifact 仍可能持續流向正式環境。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：管理入口採最小權限與網段隔離（VPN-only / 內網 only、不直接外網可達），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。&lt;/li>
&lt;li>日常：建立 build 異常行為與 pipeline 變更告警（unauthorized build trigger / 異常 build script 變更）。&lt;/li>
&lt;li>事故中：凍結部署、驗證 artifact、分批恢復發佈（前提是 artifact 有 provenance 可追溯 build 是否在事件窗口內產生）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成 CI 凍結演練、artifact 證據鏈。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的 CI/CD 交付信任鏈、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的凍結與回復節奏。&lt;/li>
&lt;/ul>
&lt;p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://blog.jetbrains.com/teamcity/2024/03/additional-critical-security-issues-affecting-teamcity-on-premises-cve-2024-27198-and-cve-2024-27199-update-to-2023-11-4-now">blog.jetbrains.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、雙漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-27198">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-27199">nvd.nist.gov/CVE-2024-27199&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、auth bypass + path traversal 機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>TeamCity 2024 事件顯示 CI 平台在認證繞過與路徑穿越漏洞同時存在時，交付鏈風險會被快速放大。</p>
<p><strong>本案例的演示焦點</strong>：CI 管理介面 auth bypass + path traversal 雙漏洞 → build pipeline 接管 → artifact 污染擴散下游。屬 CI-platform entrypoint 漏洞鏈、跟 SolarWinds 類 build-time 植入互補。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者鎖定可達 TeamCity 管理入口。</li>
<li>利用 CVE-2024-27198 或 CVE-2024-27199 取得未授權能力。</li>
<li>觸及 build 任務與 artifact 交付路徑。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>CI 管理介面隔離與存取控制不足。</li>
<li>交付鏈完整性驗證節奏不足。</li>
<li>事件時部署凍結與回退流程聯動不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「CI 事件即凍結交付」步驟，受影響 artifact 仍可能持續流向正式環境。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：管理入口採最小權限與網段隔離（VPN-only / 內網 only、不直接外網可達），mechanism 是讓 entrypoint 漏洞先碰到網段邊界。</li>
<li>日常：建立 build 異常行為與 pipeline 變更告警（unauthorized build trigger / 異常 build script 變更）。</li>
<li>事故中：凍結部署、驗證 artifact、分批恢復發佈（前提是 artifact 有 provenance 可追溯 build 是否在事件窗口內產生）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/supply-chain-integrity-and-artifact-trust/" data-link-title="7.12 供應鏈完整性與 Artifact 信任" data-link-desc="定義 build provenance、artifact 信任與交付鏈風險問題">7.6 供應鏈完整性與 artifact 信任</a> + <a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/supply-chain-artifact-drill/" data-link-title="Supply Chain Artifact Drill" data-link-desc="以 artifact provenance 偏移設計供應鏈 release gate 與 rollback 演練">Supply chain artifact drill</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成 CI 凍結演練、artifact 證據鏈。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的 CI/CD 交付信任鏈、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的凍結與回復節奏。</li>
</ul>
<p>供應鏈類事故不對應紅隊 problem-cards，主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://blog.jetbrains.com/teamcity/2024/03/additional-critical-security-issues-affecting-teamcity-on-premises-cve-2024-27198-and-cve-2024-27199-update-to-2023-11-4-now">blog.jetbrains.com</a></td>
          <td>官方</td>
          <td>受影響版本、雙漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-27198">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-27199">nvd.nist.gov/CVE-2024-27199</a></td>
          <td>技術分析</td>
          <td>CVE 細節、auth bypass + path traversal 機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.10 Juniper 2023：網通設備鏈式漏洞窗口</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/juniper-cve-2023-36844-vpn-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/juniper-cve-2023-36844-vpn-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Juniper CVE-2023-36844 系列事件說明網通設備鏈式漏洞會同時帶來控制平面與營運穩定性壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>探測可達的設備服務面。&lt;/li>
&lt;li>串接漏洞取得更高控制權。&lt;/li>
&lt;li>對路由、連線與管理平面產生影響。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>設備風險分級與修補窗口管理不足。&lt;/li>
&lt;li>流量切換預案與維護時序不足。&lt;/li>
&lt;li>變更後驗證與回退流程定義不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「分區修補與流量切換」步驟，單次變更可能同時放大安全與可用性風險。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>發布前：建立設備分區與維護窗口策略。&lt;/li>
&lt;li>日常：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness&lt;/a> 驗證回退路徑可行性。&lt;/li>
&lt;li>事故中：依業務優先級分區修補並持續驗證。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://supportportal.juniper.net/JSA72300">supportportal.juniper.net&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-36844">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-36844">nvd.nist.gov/CVE-2023-36844&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Juniper CVE-2023-36844 系列事件說明網通設備鏈式漏洞會同時帶來控制平面與營運穩定性壓力。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>探測可達的設備服務面。</li>
<li>串接漏洞取得更高控制權。</li>
<li>對路由、連線與管理平面產生影響。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>設備風險分級與修補窗口管理不足。</li>
<li>流量切換預案與維護時序不足。</li>
<li>變更後驗證與回退流程定義不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「分區修補與流量切換」步驟，單次變更可能同時放大安全與可用性風險。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>發布前：建立設備分區與維護窗口策略。</li>
<li>日常：以 <a href="/blog/backend/knowledge-cards/readiness/" data-link-title="Readiness" data-link-desc="說明 instance 何時可以安全接收流量，以及 readiness 如何和部署平台協作">readiness</a> 驗證回退路徑可行性。</li>
<li>事故中：依業務優先級分區修補並持續驗證。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://supportportal.juniper.net/JSA72300">supportportal.juniper.net</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-36844">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-36844">nvd.nist.gov/CVE-2023-36844</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.11 ServiceNow 2024：企業平台入口風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/servicenow-cve-2024-4879-enterprise-platform/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/servicenow-cve-2024-4879-enterprise-platform/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>ServiceNow CVE-2024-4879/5217 類事件提醒企業核心平台若出現入口風險，影響會直接跨到多部門流程。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定對外或可達平台入口。&lt;/li>
&lt;li>利用漏洞取得平台層能力。&lt;/li>
&lt;li>影響工單、流程自動化或資料操作。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>平台管理與業務流程耦合度高。&lt;/li>
&lt;li>高權限操作缺少額外防護步驟。&lt;/li>
&lt;li>平台事件時的流程降級機制不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「平台事件分級切換」步驟，團隊會同時承受安全處置與流程停滯壓力。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：關鍵平台操作建立雙層授權。&lt;/li>
&lt;li>日常：對高風險變更設置審核與回退標準。&lt;/li>
&lt;li>事故中：優先保護核心流程並分批恢復平台能力。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://support.servicenow.com/kb?id=kb_article_view&amp;amp;sysparm_article=KB1645154">support.servicenow.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-4879">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-4879">nvd.nist.gov/CVE-2024-4879&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>ServiceNow CVE-2024-4879/5217 類事件提醒企業核心平台若出現入口風險，影響會直接跨到多部門流程。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定對外或可達平台入口。</li>
<li>利用漏洞取得平台層能力。</li>
<li>影響工單、流程自動化或資料操作。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>平台管理與業務流程耦合度高。</li>
<li>高權限操作缺少額外防護步驟。</li>
<li>平台事件時的流程降級機制不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「平台事件分級切換」步驟，團隊會同時承受安全處置與流程停滯壓力。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：關鍵平台操作建立雙層授權。</li>
<li>日常：對高風險變更設置審核與回退標準。</li>
<li>事故中：優先保護核心流程並分批恢復平台能力。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://support.servicenow.com/kb?id=kb_article_view&amp;sysparm_article=KB1645154">support.servicenow.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-4879">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-4879">nvd.nist.gov/CVE-2024-4879</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.12 Check Point 2024：VPN 資訊外洩與會話風險</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/check-point-cve-2024-24919-vpn-info-disclosure/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/check-point-cve-2024-24919-vpn-info-disclosure/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Check Point CVE-2024-24919 事件顯示資訊外洩類漏洞在 VPN 邊界可直接放大身分與會話風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>針對可達 VPN 入口發動利用。&lt;/li>
&lt;li>擷取可用會話或認證相關資訊。&lt;/li>
&lt;li>轉換成未授權存取與橫向探索。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>VPN 邊界資訊保護與失效策略不足。&lt;/li>
&lt;li>會話生命週期管理與輪替機制不足。&lt;/li>
&lt;li>事件後整體收斂流程缺少標準化。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「全域會話失效」步驟，攻擊者可延長已取得存取的有效時間。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：將 VPN 會話管理納入失效與重發機制。&lt;/li>
&lt;li>日常：對會話異常地理來源建立告警。&lt;/li>
&lt;li>事故中：先做會話失效，再執行修補與重驗證。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://support.checkpoint.com/results/sk/sk182336">support.checkpoint.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-24919">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-24919">nvd.nist.gov/CVE-2024-24919&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Check Point CVE-2024-24919 事件顯示資訊外洩類漏洞在 VPN 邊界可直接放大身分與會話風險。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>針對可達 VPN 入口發動利用。</li>
<li>擷取可用會話或認證相關資訊。</li>
<li>轉換成未授權存取與橫向探索。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>VPN 邊界資訊保護與失效策略不足。</li>
<li>會話生命週期管理與輪替機制不足。</li>
<li>事件後整體收斂流程缺少標準化。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「全域會話失效」步驟，攻擊者可延長已取得存取的有效時間。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：將 VPN 會話管理納入失效與重發機制。</li>
<li>日常：對會話異常地理來源建立告警。</li>
<li>事故中：先做會話失效，再執行修補與重驗證。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://support.checkpoint.com/results/sk/sk182336">support.checkpoint.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2024-24919">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2024-24919">nvd.nist.gov/CVE-2024-24919</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.13 ProxyLogon 2021：CVE-2021-26855/27065 入口鏈式失效</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxylogon-2021-exchange-entry-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxylogon-2021-exchange-entry-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>ProxyLogon 事件顯示 CVE-2021-26855 與 CVE-2021-27065 這類企業郵件系統入口漏洞可被快速批量利用並形成內網風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2021-26855 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描 Exchange 對外入口。&lt;/li>
&lt;li>串接漏洞取得伺服器執行能力。&lt;/li>
&lt;li>植入 web shell 或建立持續控制。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>郵件入口暴露與修補時差偏大。&lt;/li>
&lt;li>漏洞利用跡象監控覆蓋不足。&lt;/li>
&lt;li>事件後清除與重建流程準備不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補後入侵痕跡清查」步驟，事件會在已更新版本上延續。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把郵件系統納入高風險資產修補路由。&lt;/li>
&lt;li>日常：追蹤異常 web shell 與命令執行行為。&lt;/li>
&lt;li>事故中：執行修補、清查、憑證輪替與重建驗證。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.microsoft.com/en-us/msrc/blog/2021/03/multiple-security-updates-released-for-exchange-server">microsoft.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-062a">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-26855">nvd.nist.gov/CVE-2021-26855&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-27065">nvd.nist.gov/CVE-2021-27065&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>ProxyLogon 事件顯示 CVE-2021-26855 與 CVE-2021-27065 這類企業郵件系統入口漏洞可被快速批量利用並形成內網風險。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2021-26855 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描 Exchange 對外入口。</li>
<li>串接漏洞取得伺服器執行能力。</li>
<li>植入 web shell 或建立持續控制。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>郵件入口暴露與修補時差偏大。</li>
<li>漏洞利用跡象監控覆蓋不足。</li>
<li>事件後清除與重建流程準備不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補後入侵痕跡清查」步驟，事件會在已更新版本上延續。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把郵件系統納入高風險資產修補路由。</li>
<li>日常：追蹤異常 web shell 與命令執行行為。</li>
<li>事故中：執行修補、清查、憑證輪替與重建驗證。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.microsoft.com/en-us/msrc/blog/2021/03/multiple-security-updates-released-for-exchange-server">microsoft.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-062a">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-26855">nvd.nist.gov/CVE-2021-26855</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-27065">nvd.nist.gov/CVE-2021-27065</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.14 ProxyShell 2021：CVE-2021-34473/34523/31207 後續鏈式攻擊</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxyshell-2021-exchange-post-auth-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/proxyshell-2021-exchange-post-auth-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>ProxyShell 事件延續了 Exchange 入口風險，顯示 CVE-2021-34473、CVE-2021-34523、CVE-2021-31207 這類多波漏洞會持續推高後續攻擊壓力。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2021-34473 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>利用 ProxyShell 鏈式漏洞取得存取。&lt;/li>
&lt;li>建立持續控制與資料探查能力。&lt;/li>
&lt;li>擴展到郵件與內部服務資產。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>同平台連續漏洞的追蹤治理不足。&lt;/li>
&lt;li>漏洞修補完成後的行為監控不足。&lt;/li>
&lt;li>事件後硬化與稽核節奏不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「波次事件重評估」步驟，團隊會以單次修補視角處理，留下後續利用窗口。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：建立同平台連續漏洞的專屬追蹤清單。&lt;/li>
&lt;li>日常：持續監控異常管理命令與資料下載行為。&lt;/li>
&lt;li>事故中：修補與風險重評估並行，直到驗證關閉。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://techcommunity.microsoft.com/blog/exchange/released-july-2021-exchange-server-security-updates/2523421">techcommunity.microsoft.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2021/08/21/urgent-protect-against-active-exploitation-proxyshell-vulnerabilities">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-34473">nvd.nist.gov/CVE-2021-34473&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-34523">nvd.nist.gov/CVE-2021-34523&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-31207">nvd.nist.gov/CVE-2021-31207&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>ProxyShell 事件延續了 Exchange 入口風險，顯示 CVE-2021-34473、CVE-2021-34523、CVE-2021-31207 這類多波漏洞會持續推高後續攻擊壓力。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2021-34473 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>利用 ProxyShell 鏈式漏洞取得存取。</li>
<li>建立持續控制與資料探查能力。</li>
<li>擴展到郵件與內部服務資產。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>同平台連續漏洞的追蹤治理不足。</li>
<li>漏洞修補完成後的行為監控不足。</li>
<li>事件後硬化與稽核節奏不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「波次事件重評估」步驟，團隊會以單次修補視角處理，留下後續利用窗口。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：建立同平台連續漏洞的專屬追蹤清單。</li>
<li>日常：持續監控異常管理命令與資料下載行為。</li>
<li>事故中：修補與風險重評估並行，直到驗證關閉。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://techcommunity.microsoft.com/blog/exchange/released-july-2021-exchange-server-security-updates/2523421">techcommunity.microsoft.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2021/08/21/urgent-protect-against-active-exploitation-proxyshell-vulnerabilities">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-34473">nvd.nist.gov/CVE-2021-34473</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-34523">nvd.nist.gov/CVE-2021-34523</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-31207">nvd.nist.gov/CVE-2021-31207</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.15 FortiOS 2022：VPN 零時差事件節奏</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortios-cve-2022-42475-vpn-zero-day/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortios-cve-2022-42475-vpn-zero-day/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>FortiOS CVE-2022-42475 事件顯示 VPN 零時差漏洞可讓攻擊者在短時間內取得邊界控制權。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定暴露於外網的 VPN 設備。&lt;/li>
&lt;li>利用零時差漏洞取得執行能力。&lt;/li>
&lt;li>建立持續存取與內網移動路徑。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>邊界設備資產盤點與優先順序不足。&lt;/li>
&lt;li>事件中憑證輪替與設備重建節奏不足。&lt;/li>
&lt;li>修補後狀態驗證與證據保存不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「事件後憑證全域輪替」步驟，已外露的認證素材會維持可用。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把關鍵 VPN 設備納入快速隔離策略。&lt;/li>
&lt;li>日常：對設備韌體版本與異常行為做固定巡檢。&lt;/li>
&lt;li>事故中：隔離、修補、輪替、重建四段並行。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.fortiguard.com/psirt/FG-IR-22-398">fortiguard.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>資安廠商深度分析、IoC、利用樣態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2022-42475">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2022-42475">nvd.nist.gov/CVE-2022-42475&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>FortiOS CVE-2022-42475 事件顯示 VPN 零時差漏洞可讓攻擊者在短時間內取得邊界控制權。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定暴露於外網的 VPN 設備。</li>
<li>利用零時差漏洞取得執行能力。</li>
<li>建立持續存取與內網移動路徑。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>邊界設備資產盤點與優先順序不足。</li>
<li>事件中憑證輪替與設備重建節奏不足。</li>
<li>修補後狀態驗證與證據保存不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「事件後憑證全域輪替」步驟，已外露的認證素材會維持可用。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把關鍵 VPN 設備納入快速隔離策略。</li>
<li>日常：對設備韌體版本與異常行為做固定巡檢。</li>
<li>事故中：隔離、修補、輪替、重建四段並行。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.fortiguard.com/psirt/FG-IR-22-398">fortiguard.com</a></td>
          <td>技術分析</td>
          <td>資安廠商深度分析、IoC、利用樣態</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2022-42475">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2022-42475">nvd.nist.gov/CVE-2022-42475</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.16 Citrix ADC 後續事件：Session 重放延伸</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-adc-2023-follow-on-session-risk/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-adc-2023-follow-on-session-risk/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Citrix 後續事件通報指出，漏洞修補後仍需處理 session 與憑證風險，才能完成真正關閉。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>利用邊界漏洞取得會話資訊。&lt;/li>
&lt;li>以重放方式維持未授權存取。&lt;/li>
&lt;li>在修補後窗口延續攻擊。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>修補流程與會話收斂流程分離。&lt;/li>
&lt;li>權杖失效策略執行覆蓋不足。&lt;/li>
&lt;li>事後追蹤指標沒有對準重放風險。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補後全域重新驗證登入」步驟，已竊取會話仍可能繼續被利用。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：定義漏洞事件後的 session 重發機制。&lt;/li>
&lt;li>日常：維護會話壽命與失效政策基線。&lt;/li>
&lt;li>事故中：修補、會話失效、登入重驗證三段同步。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://support.citrix.com/article/CTX579459">support.citrix.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/news-events/alerts/2023/11/07/cisa-releases-guidance-addressing-citrix-netscaler-adc-and-gateway-vulnerability-cve-2023-4966">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>受影響範圍、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4966">nvd.nist.gov/CVE-2023-4966&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Citrix 後續事件通報指出，漏洞修補後仍需處理 session 與憑證風險，才能完成真正關閉。</p>
<p><strong>本案例的演示焦點</strong>：邊界 zero-day → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>利用邊界漏洞取得會話資訊。</li>
<li>以重放方式維持未授權存取。</li>
<li>在修補後窗口延續攻擊。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>修補流程與會話收斂流程分離。</li>
<li>權杖失效策略執行覆蓋不足。</li>
<li>事後追蹤指標沒有對準重放風險。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補後全域重新驗證登入」步驟，已竊取會話仍可能繼續被利用。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：定義漏洞事件後的 session 重發機制。</li>
<li>日常：維護會話壽命與失效政策基線。</li>
<li>事故中：修補、會話失效、登入重驗證三段同步。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://support.citrix.com/article/CTX579459">support.citrix.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/news-events/alerts/2023/11/07/cisa-releases-guidance-addressing-citrix-netscaler-adc-and-gateway-vulnerability-cve-2023-4966">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>受影響範圍、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4966">nvd.nist.gov/CVE-2023-4966</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.17 Confluence 2023：CVE-2023-22515/22518 權限控制鏈</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-2023-cve-22515-22518-access-control-chain/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/confluence-2023-cve-22515-22518-access-control-chain/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>Confluence 2023 連續漏洞事件顯示權限控制面一旦失效，協作平台會快速變成攻擊者的入口節點。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-22515 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者鎖定對外 Confluence 節點。&lt;/li>
&lt;li>利用 CVE-2023-22515 或 CVE-2023-22518 取得未授權存取能力。&lt;/li>
&lt;li>透過已取得權限接觸內部文件與後續線索。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>協作平台權限模型與網段隔離耦合不足。&lt;/li>
&lt;li>連續漏洞波次的修補節奏缺少統一追蹤。&lt;/li>
&lt;li>高風險入口事件與稽核流程連動不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「同平台連續漏洞重評估」步驟，團隊會用單點修補視角處理，留下後續利用窗口。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把 Confluence 納入高風險外網資產清單與修補 SLA。&lt;/li>
&lt;li>日常：建立協作平台異常管理行為告警。&lt;/li>
&lt;li>事故中：入口隔離、補丁套用、管理憑證收斂並行執行。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://confluence.atlassian.com/display/SECURITY/CVE-2023-22515%2B-%2BBroken%2BAccess%2BControl%2BVulnerability%2Bin%2BConfluence%2BData%2BCenter%2Band%2BServer">confluence.atlassian.com/CVE-2023-22515&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://confluence.atlassian.com/security/cve-2023-22518-improper-authorization-vulnerability-in-confluence-data-center-and-confluence-server-1311473907.html">confluence.atlassian.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-22515">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-22518">nvd.nist.gov/CVE-2023-22518&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>Confluence 2023 連續漏洞事件顯示權限控制面一旦失效，協作平台會快速變成攻擊者的入口節點。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-22515 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者鎖定對外 Confluence 節點。</li>
<li>利用 CVE-2023-22515 或 CVE-2023-22518 取得未授權存取能力。</li>
<li>透過已取得權限接觸內部文件與後續線索。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>協作平台權限模型與網段隔離耦合不足。</li>
<li>連續漏洞波次的修補節奏缺少統一追蹤。</li>
<li>高風險入口事件與稽核流程連動不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「同平台連續漏洞重評估」步驟，團隊會用單點修補視角處理，留下後續利用窗口。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把 Confluence 納入高風險外網資產清單與修補 SLA。</li>
<li>日常：建立協作平台異常管理行為告警。</li>
<li>事故中：入口隔離、補丁套用、管理憑證收斂並行執行。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://confluence.atlassian.com/display/SECURITY/CVE-2023-22515%2B-%2BBroken%2BAccess%2BControl%2BVulnerability%2Bin%2BConfluence%2BData%2BCenter%2Band%2BServer">confluence.atlassian.com/CVE-2023-22515</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://confluence.atlassian.com/security/cve-2023-22518-improper-authorization-vulnerability-in-confluence-data-center-and-confluence-server-1311473907.html">confluence.atlassian.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-22515">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-22518">nvd.nist.gov/CVE-2023-22518</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.18 Citrix 2023：CVE-2023-3519 邊界代碼注入</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-cve-2023-3519-code-injection/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/citrix-cve-2023-3519-code-injection/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2023-3519 事件顯示 NetScaler 這類邊界設備的代碼注入漏洞可迅速轉成控制平面風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-3519 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>探測暴露的 NetScaler 服務面。&lt;/li>
&lt;li>利用代碼注入漏洞取得執行能力。&lt;/li>
&lt;li>透過邊界節點延伸到內部網路路徑。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>邊界設備暴露面治理不足。&lt;/li>
&lt;li>修補窗口內缺少臨時緩解策略。&lt;/li>
&lt;li>修補後狀態驗證與稽核覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「修補前入口緩解」步驟，攻擊者可在公告窗口內持續利用邊界節點。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：把高風險邊界設備納入專用維護窗口。&lt;/li>
&lt;li>日常：持續盤點外網可達管理入口。&lt;/li>
&lt;li>事故中：先限縮入口，再分區修補與抽樣復測。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://support.citrix.com/external/article/CTX561482/citrix-adc-and-citrix-gateway-security-b.html">support.citrix.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-3519">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3519">nvd.nist.gov/CVE-2023-3519&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2023-3519 事件顯示 NetScaler 這類邊界設備的代碼注入漏洞可迅速轉成控制平面風險。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-3519 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>探測暴露的 NetScaler 服務面。</li>
<li>利用代碼注入漏洞取得執行能力。</li>
<li>透過邊界節點延伸到內部網路路徑。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>邊界設備暴露面治理不足。</li>
<li>修補窗口內缺少臨時緩解策略。</li>
<li>修補後狀態驗證與稽核覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「修補前入口緩解」步驟，攻擊者可在公告窗口內持續利用邊界節點。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：把高風險邊界設備納入專用維護窗口。</li>
<li>日常：持續盤點外網可達管理入口。</li>
<li>事故中：先限縮入口，再分區修補與抽樣復測。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://support.citrix.com/external/article/CTX561482/citrix-adc-and-citrix-gateway-security-b.html">support.citrix.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-3519">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3519">nvd.nist.gov/CVE-2023-3519</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.19 F5 BIG-IP 2023：CVE-2023-46747 認證繞過</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/f5-bigip-cve-2023-46747-auth-bypass/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/f5-bigip-cve-2023-46747-auth-bypass/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2023-46747 事件指出 BIG-IP 組態工具一旦出現認證繞過，邊界治理會承受高壓。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-46747 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定可達 BIG-IP 管理入口。&lt;/li>
&lt;li>利用認證繞過取得管理能力。&lt;/li>
&lt;li>影響設備配置與流量控制策略。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理平面隔離策略覆蓋不足。&lt;/li>
&lt;li>設備設定變更的稽核強度不足。&lt;/li>
&lt;li>事件時的快速封鎖與回復路徑準備不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「管理平面緊急鎖定」步驟，攻擊者可利用高權限配置能力持續擴散影響。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：管理介面改為受控網段與跳板存取。&lt;/li>
&lt;li>日常：建立設備配置差異與異常變更告警。&lt;/li>
&lt;li>事故中：鎖定管理入口、收斂憑證、恢復最小可用設定。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://my.f5.com/manage/s/article/K000137353">my.f5.com&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-46747">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-46747">nvd.nist.gov/CVE-2023-46747&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2023-46747 事件指出 BIG-IP 組態工具一旦出現認證繞過，邊界治理會承受高壓。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-46747 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定可達 BIG-IP 管理入口。</li>
<li>利用認證繞過取得管理能力。</li>
<li>影響設備配置與流量控制策略。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理平面隔離策略覆蓋不足。</li>
<li>設備設定變更的稽核強度不足。</li>
<li>事件時的快速封鎖與回復路徑準備不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「管理平面緊急鎖定」步驟，攻擊者可利用高權限配置能力持續擴散影響。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：管理介面改為受控網段與跳板存取。</li>
<li>日常：建立設備配置差異與異常變更告警。</li>
<li>事故中：鎖定管理入口、收斂憑證、恢復最小可用設定。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://my.f5.com/manage/s/article/K000137353">my.f5.com</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-46747">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-46747">nvd.nist.gov/CVE-2023-46747</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.20 Fortinet 2022：CVE-2022-40684 認證繞過</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2022-40684-auth-bypass/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2022-40684-auth-bypass/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2022-40684 事件顯示 Fortinet 多產品在認證繞過情境下，邊界與管理面風險會同步上升。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2022-40684 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>掃描可達管理或邊界節點。&lt;/li>
&lt;li>利用認證繞過取得未授權管理能力。&lt;/li>
&lt;li>調整設備策略並擴大內網風險面。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理入口防護層次不足。&lt;/li>
&lt;li>高風險設備的修補節奏不一致。&lt;/li>
&lt;li>變更稽核與異常追蹤鏈路不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「設備配置完整性復核」步驟，修補完成後仍可能維持高風險配置狀態。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：對高權限管理面建立多層存取限制。&lt;/li>
&lt;li>日常：以固定節奏審核設備配置與管理帳號。&lt;/li>
&lt;li>事故中：修補、配置復核、憑證輪替同步執行。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.fortiguard.com/psirt/FG-IR-22-377">fortiguard.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>資安廠商深度分析、IoC、利用樣態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2022-40684">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2022-40684">nvd.nist.gov/CVE-2022-40684&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2022-40684 事件顯示 Fortinet 多產品在認證繞過情境下，邊界與管理面風險會同步上升。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2022-40684 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>掃描可達管理或邊界節點。</li>
<li>利用認證繞過取得未授權管理能力。</li>
<li>調整設備策略並擴大內網風險面。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理入口防護層次不足。</li>
<li>高風險設備的修補節奏不一致。</li>
<li>變更稽核與異常追蹤鏈路不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「設備配置完整性復核」步驟，修補完成後仍可能維持高風險配置狀態。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：對高權限管理面建立多層存取限制。</li>
<li>日常：以固定節奏審核設備配置與管理帳號。</li>
<li>事故中：修補、配置復核、憑證輪替同步執行。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.fortiguard.com/psirt/FG-IR-22-377">fortiguard.com</a></td>
          <td>技術分析</td>
          <td>資安廠商深度分析、IoC、利用樣態</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2022-40684">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2022-40684">nvd.nist.gov/CVE-2022-40684</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.21 Fortinet 2023：CVE-2023-27997 SSL-VPN 溢位</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2023-27997-sslvpn-overflow/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/fortinet-cve-2023-27997-sslvpn-overflow/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2023-27997 事件顯示 SSL-VPN 漏洞在邊界設備上具備高利用效率與高傳播風險。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-27997 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>鎖定外網可達 SSL-VPN 節點。&lt;/li>
&lt;li>利用溢位漏洞取得執行或控制能力。&lt;/li>
&lt;li>沿著 VPN 通道進一步探索內部資產。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>外網暴露設備缺少即時緩解策略。&lt;/li>
&lt;li>高風險漏洞的修補優先級路由不足。&lt;/li>
&lt;li>事件後會話與憑證收斂速度不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「漏洞公告即資產分級處置」步驟，團隊會在關鍵窗口失去修補優先順序。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：建立 VPN 高風險資產清單與替代連線路由。&lt;/li>
&lt;li>日常：監控異常登入行為與會話模式變化。&lt;/li>
&lt;li>事故中：隔離節點、修補復測、全域會話失效並行。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.fortiguard.com/psirt/FG-IR-23-097">fortiguard.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>資安廠商深度分析、IoC、利用樣態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-27997">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-27997">nvd.nist.gov/CVE-2023-27997&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2023-27997 事件顯示 SSL-VPN 漏洞在邊界設備上具備高利用效率與高傳播風險。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-27997 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>鎖定外網可達 SSL-VPN 節點。</li>
<li>利用溢位漏洞取得執行或控制能力。</li>
<li>沿著 VPN 通道進一步探索內部資產。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>外網暴露設備缺少即時緩解策略。</li>
<li>高風險漏洞的修補優先級路由不足。</li>
<li>事件後會話與憑證收斂速度不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「漏洞公告即資產分級處置」步驟，團隊會在關鍵窗口失去修補優先順序。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：建立 VPN 高風險資產清單與替代連線路由。</li>
<li>日常：監控異常登入行為與會話模式變化。</li>
<li>事故中：隔離節點、修補復測、全域會話失效並行。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.fortiguard.com/psirt/FG-IR-23-097">fortiguard.com</a></td>
          <td>技術分析</td>
          <td>資安廠商深度分析、IoC、利用樣態</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-27997">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-27997">nvd.nist.gov/CVE-2023-27997</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.22 FortiClient EMS 2023：CVE-2023-48788 SQL 注入</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/forticlient-ems-cve-2023-48788-sqli/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/forticlient-ems-cve-2023-48788-sqli/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2023-48788 事件反映端點管理平台一旦受 SQL 注入影響，管理資料與權限邊界會同步承壓。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2023-48788 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>攻擊者定位可達 EMS 管理介面。&lt;/li>
&lt;li>利用 SQL 注入取得未授權資料或控制能力。&lt;/li>
&lt;li>進一步影響端點管理與政策下發流程。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>管理平台資料層保護不足。&lt;/li>
&lt;li>管理平面與業務平面隔離不足。&lt;/li>
&lt;li>高風險查詢與異常操作告警不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「管理平面異常資料操作收斂」步驟，攻擊者可延長停留並提高橫向擴散機率。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：對管理 API 與資料層建立最小暴露策略。&lt;/li>
&lt;li>日常：監控高風險查詢與管理變更異常。&lt;/li>
&lt;li>事故中：隔離管理節點、收斂權限、驗證資料完整性。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://fortiguard.com/psirt/FG-IR-24-007">fortiguard.com&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>資安廠商深度分析、IoC、利用樣態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-48788">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2023-48788">nvd.nist.gov/CVE-2023-48788&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2023-48788 事件反映端點管理平台一旦受 SQL 注入影響，管理資料與權限邊界會同步承壓。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2023-48788 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>攻擊者定位可達 EMS 管理介面。</li>
<li>利用 SQL 注入取得未授權資料或控制能力。</li>
<li>進一步影響端點管理與政策下發流程。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>管理平台資料層保護不足。</li>
<li>管理平面與業務平面隔離不足。</li>
<li>高風險查詢與異常操作告警不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「管理平面異常資料操作收斂」步驟，攻擊者可延長停留並提高橫向擴散機率。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：對管理 API 與資料層建立最小暴露策略。</li>
<li>日常：監控高風險查詢與管理變更異常。</li>
<li>事故中：隔離管理節點、收斂權限、驗證資料完整性。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://fortiguard.com/psirt/FG-IR-24-007">fortiguard.com</a></td>
          <td>技術分析</td>
          <td>資安廠商深度分析、IoC、利用樣態</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2023-48788">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2023-48788">nvd.nist.gov/CVE-2023-48788</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.23 ManageEngine 2021：CVE-2021-40539 認證繞過</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/manageengine-adself-cve-2021-40539-auth-bypass/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/manageengine-adself-cve-2021-40539-auth-bypass/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2021-40539 事件顯示身分管理服務的認證繞過漏洞可直接影響企業帳號與授權流程。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2021-40539 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>探測 ADSelfService Plus 入口。&lt;/li>
&lt;li>利用認證繞過取得管理能力。&lt;/li>
&lt;li>觸及帳號管理與身分資料流程。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>身分管理入口隔離不足。&lt;/li>
&lt;li>管理操作二次驗證與審批不足。&lt;/li>
&lt;li>身分平台事件與全域憑證輪替聯動不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「身分平台事件觸發全域收斂」步驟，攻擊者可利用管理能力放大影響面。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：身分管理入口改為專用網段與跳板策略。&lt;/li>
&lt;li>日常：高風險帳號動作納入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook&lt;/a>。&lt;/li>
&lt;li>事故中：封鎖入口、輪替憑證、審核高權限帳號變更。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.manageengine.com/products/self-service-password/advisory/CVE-2021-40539.html">manageengine.com/CVE-2021-40539&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-40539">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-40539">nvd.nist.gov/CVE-2021-40539&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2021-40539 事件顯示身分管理服務的認證繞過漏洞可直接影響企業帳號與授權流程。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2021-40539 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>探測 ADSelfService Plus 入口。</li>
<li>利用認證繞過取得管理能力。</li>
<li>觸及帳號管理與身分資料流程。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>身分管理入口隔離不足。</li>
<li>管理操作二次驗證與審批不足。</li>
<li>身分平台事件與全域憑證輪替聯動不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「身分平台事件觸發全域收斂」步驟，攻擊者可利用管理能力放大影響面。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：身分管理入口改為專用網段與跳板策略。</li>
<li>日常：高風險帳號動作納入 <a href="/blog/backend/knowledge-cards/alert-runbook/" data-link-title="Alert Runbook" data-link-desc="說明告警如何連到可執行的排障與恢復流程">alert runbook</a>。</li>
<li>事故中：封鎖入口、輪替憑證、審核高權限帳號變更。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.manageengine.com/products/self-service-password/advisory/CVE-2021-40539.html">manageengine.com/CVE-2021-40539</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-40539">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-40539">nvd.nist.gov/CVE-2021-40539</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>7.R7.3.24 USAHERDS 2021：CVE-2021-44207 硬編碼憑證</title><link>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/usaherds-cve-2021-44207-hardcoded-credential/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/07-security-data-protection/red-team/cases/edge-exposure/usaherds-cve-2021-44207-hardcoded-credential/</guid><description>&lt;h2 id="事故摘要">事故摘要&lt;/h2>
&lt;p>CVE-2021-44207 事件顯示硬編碼憑證一旦被識別，攻擊者可沿著固定認證路徑持續進入系統。&lt;/p>
&lt;p>&lt;strong>本案例的演示焦點&lt;/strong>：該CVE-2021-44207 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。&lt;/p>
&lt;h2 id="攻擊路徑">攻擊路徑&lt;/h2>
&lt;ol>
&lt;li>逆向或檢索系統中可重用憑證。&lt;/li>
&lt;li>利用硬編碼憑證取得入口存取能力。&lt;/li>
&lt;li>以固定認證模式維持長期可用存取。&lt;/li>
&lt;/ol>
&lt;h2 id="失效控制面">失效控制面&lt;/h2>
&lt;ul>
&lt;li>憑證生命週期治理缺少輪替與淘汰。&lt;/li>
&lt;li>應用程式配置審查未涵蓋硬編碼風險。&lt;/li>
&lt;li>憑證異常使用監控覆蓋不足。&lt;/li>
&lt;/ul>
&lt;h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼&lt;/h2>
&lt;p>若少了「憑證來源掃描與輪替」步驟，事件會在修補後保留同類風險模式。&lt;/p>
&lt;h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點&lt;/h2>
&lt;ul>
&lt;li>共同基線：以 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 固定記錄觸發條件與處置節奏。&lt;/li>
&lt;li>發布前：建立配置掃描規則，攔截硬編碼憑證。&lt;/li>
&lt;li>日常：把憑證輪替與金鑰盤點納入固定排程。&lt;/li>
&lt;li>事故中：封鎖舊憑證、完成全域替換與歷史比對。&lt;/li>
&lt;li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。&lt;/li>
&lt;/ul>
&lt;h2 id="從本案例到實作的-chain">從本案例到實作的 chain&lt;/h2>
&lt;p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>控制面&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理&lt;/a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。&lt;/li>
&lt;li>&lt;strong>演練 / 控制落地&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern&lt;/a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。&lt;/li>
&lt;li>&lt;strong>跨章交接&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform&lt;/a> 的邊界部署治理、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response&lt;/a> 的調查與回復步驟。&lt;/li>
&lt;/ul>
&lt;p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。&lt;/p>
&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;a href="https://www.cve.org/CVERecord?id=CVE-2021-44207">cve.org&lt;/a>&lt;/td>
 &lt;td>官方&lt;/td>
 &lt;td>受影響版本、漏洞細節、修補節奏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-44207">cisa.gov&lt;/a>&lt;/td>
 &lt;td>政府/監管&lt;/td>
 &lt;td>KEV 列入、跨機構處置建議&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44207">nvd.nist.gov/CVE-2021-44207&lt;/a>&lt;/td>
 &lt;td>技術分析&lt;/td>
 &lt;td>CVE 細節、利用機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table></description><content:encoded><![CDATA[<h2 id="事故摘要">事故摘要</h2>
<p>CVE-2021-44207 事件顯示硬編碼憑證一旦被識別，攻擊者可沿著固定認證路徑持續進入系統。</p>
<p><strong>本案例的演示焦點</strong>：該CVE-2021-44207 → 邊界設備 / 對外應用入口接管 → 內部資源 / 會話 / 資料的橫向擴散。屬於 edge-exposure 類別、跟身分鏈接管 / 供應鏈植入 / 資料外送等其他 case category 形成互補視角。</p>
<h2 id="攻擊路徑">攻擊路徑</h2>
<ol>
<li>逆向或檢索系統中可重用憑證。</li>
<li>利用硬編碼憑證取得入口存取能力。</li>
<li>以固定認證模式維持長期可用存取。</li>
</ol>
<h2 id="失效控制面">失效控制面</h2>
<ul>
<li>憑證生命週期治理缺少輪替與淘汰。</li>
<li>應用程式配置審查未涵蓋硬編碼風險。</li>
<li>憑證異常使用監控覆蓋不足。</li>
</ul>
<h2 id="如果-workflow-少一步會發生什麼">如果 workflow 少一步會發生什麼</h2>
<p>若少了「憑證來源掃描與輪替」步驟，事件會在修補後保留同類風險模式。</p>
<h2 id="可落地的-workflow-檢查點">可落地的 workflow 檢查點</h2>
<ul>
<li>共同基線：以 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 固定記錄觸發條件與處置節奏。</li>
<li>發布前：建立配置掃描規則，攔截硬編碼憑證。</li>
<li>日常：把憑證輪替與金鑰盤點納入固定排程。</li>
<li>事故中：封鎖舊憑證、完成全域替換與歷史比對。</li>
<li>mechanism 總綱：邊界事件的核心是讓「漏洞修補」「會話 / 憑證失效」「異常痕跡清查」三件事同步發生、不分先後留下時間窗口（前提是事先有 inventory + 自動化失效 / 清查能力）。</li>
</ul>
<h2 id="從本案例到實作的-chain">從本案例到實作的 chain</h2>
<p>本案例是事故敘事 layer，沿三步 chain 進入 implementation：</p>
<ul>
<li><strong>控制面</strong>：<a href="/blog/backend/07-security-data-protection/entrypoint-and-server-protection/" data-link-title="7.3 入口治理與伺服器防護" data-link-desc="以問題驅動方式整理對外入口、管理平面與伺服器邊界">7.3 入口與伺服端保護</a> + <a href="/blog/backend/07-security-data-protection/detection-coverage-and-signal-governance/" data-link-title="7.13 偵測覆蓋率與訊號治理" data-link-desc="定義偵測覆蓋、訊號品質與誤報成本的治理問題">7.12 偵測涵蓋與訊號治理</a> —— mitigation 的 mechanism / 前提 / context-dependence 在這裡定義。</li>
<li><strong>演練 / 控制落地</strong>：<a href="/blog/backend/07-security-data-protection/blue-team/materials/scenarios/edge-session-hijack-game-day/" data-link-title="Edge Session Hijack Game Day" data-link-desc="以入口設備 session disclosure 風險設計 edge exposure game day">Edge session hijack game day</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/vulnerability-response-pattern/" data-link-title="Vulnerability Response Pattern" data-link-desc="定義漏洞回應如何從 observed 推進到 assessed、mitigated、patched、validated 與 closed">Vulnerability response pattern</a> + <a href="/blog/backend/07-security-data-protection/blue-team/materials/control-patterns/evidence-chain-pattern/" data-link-title="Evidence Chain Pattern" data-link-desc="定義事故與演練需要保存的訊號、決策、artifact、timeline 與 retention 證據">Evidence chain pattern</a> —— 把樣式轉成邊界演練、漏洞處理與證據鏈欄位。</li>
<li><strong>跨章交接</strong>：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend/05-deployment-platform</a> 的邊界部署治理、<a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">backend/08-incident-response</a> 的調查與回復步驟。</li>
</ul>
<p>本案例屬於邊界 / 入口漏洞類別、不對應紅隊 problem-cards（後者集中於 tenant flow / identity flow），主要 chain 直接從控制面起步。</p>
<h2 id="來源">來源</h2>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>類型</th>
          <th>可引用範圍</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://www.cve.org/CVERecord?id=CVE-2021-44207">cve.org</a></td>
          <td>官方</td>
          <td>受影響版本、漏洞細節、修補節奏</td>
      </tr>
      <tr>
          <td><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-44207">cisa.gov</a></td>
          <td>政府/監管</td>
          <td>KEV 列入、跨機構處置建議</td>
      </tr>
      <tr>
          <td><a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44207">nvd.nist.gov/CVE-2021-44207</a></td>
          <td>技術分析</td>
          <td>CVE 細節、利用機制</td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>Cards-Skills 系統的活案例：從一個 search bug 到 14 張新卡的閉環</title><link>https://tarrragon.github.io/blog/posts/cards-skills-%E7%B3%BB%E7%B5%B1%E7%9A%84%E6%B4%BB%E6%A1%88%E4%BE%8B%E5%BE%9E%E4%B8%80%E5%80%8B-search-bug-%E5%88%B0-14-%E5%BC%B5%E6%96%B0%E5%8D%A1%E7%9A%84%E9%96%89%E7%92%B0/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/posts/cards-skills-%E7%B3%BB%E7%B5%B1%E7%9A%84%E6%B4%BB%E6%A1%88%E4%BE%8B%E5%BE%9E%E4%B8%80%E5%80%8B-search-bug-%E5%88%B0-14-%E5%BC%B5%E6%96%B0%E5%8D%A1%E7%9A%84%E9%96%89%E7%92%B0/</guid><description>&lt;h2 id="這篇要說什麼">這篇要說什麼&lt;/h2>
&lt;p>&lt;code>content/report/&lt;/code> 累積了 70+ 張原子化事後檢討卡片、&lt;code>.claude/skills/&lt;/code> 收錄三個 protocol skill。這些是用來指導下一輪實作、又會被下一輪實作的學習回流修正的活基礎建設。&lt;/p>
&lt;p>本文把這套系統實際跑一輪的歷程紀錄下來、當未來「想用這套系統的人」的 onboarding case study。主軸是修一個 search filter bug — 看似一週工作、實際走完八輪迭代、產出 14 張新卡片 + 兩個 skill 的 v0.2 + 4 個 CI test、過程中還抓到自己的 dogfooding 失敗、回頭修一次。&lt;/p>
&lt;hr>
&lt;h2 id="起點使用者問題">起點：使用者問題&lt;/h2>
&lt;p>&amp;ldquo;我們搜尋頁的 標題/內文篩選功能現在雖然做出來了、但是還是有一個很嚴重的 BUG&amp;rdquo;&lt;/p>
&lt;p>具體：Pagefind 分批 load、view 層 post-filter；切到 title-only 後、第二批 load more 的 8 筆全部 title 不含 query → 全 hidden、畫面閃但內容沒變、使用者看到「load more 沒效果」silent 失敗。&lt;/p>
&lt;p>User 還明確補了一句：「&lt;strong>所以除了用 JS 取巧解決畫面、但是實際功能面上怎麼配合跟實作 我們並沒有解決&lt;/strong>」— 這已經點到核心：問題不在畫面、在抽象層。&lt;/p>
&lt;hr>
&lt;h2 id="第一輪拆卡片之前先想清楚">第一輪：拆卡片之前先想清楚&lt;/h2>
&lt;p>直接修 bug 是可選但不是 user 要的。User 強調：「&lt;strong>先思考我的需求、然後思考各種狀況的邊界&lt;/strong>」。&lt;/p>
&lt;p>依當時的兩個 skill — &lt;code>requirement-protocol&lt;/code>（對話協議）跟 &lt;code>frontend-with-playwright&lt;/code>（前端執行協議）— 把問題分解：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Bug 的結構性根因&lt;/strong>：filter 寫在視覺層、source 在資料層分批、兩層的「一筆」定義不一致 → silent 缺口&lt;/li>
&lt;li>&lt;strong>解法策略空間&lt;/strong>：5 個合理選項（推進 query / 自動續抓 / 多 index / 誠實 UX / 明示縮小）— 每個機會成本不同&lt;/li>
&lt;li>&lt;strong>跨領域通用性&lt;/strong>：這結構不只前端有 — 後端 middleware filter、map-reduce、SQL view 都同模式&lt;/li>
&lt;/ol>
&lt;p>User 的關鍵回應：「&lt;strong>這部份可以補充 SKILL 中演算法不足的原因 &amp;hellip; 卡片是經過多次迭代、擴充、然後分拆、再擴充、最後做連結&lt;/strong>」。&lt;/p>
&lt;p>明確了協作方式：先建卡片、再灌進 skill、最後才修。卡片本身要走原子化拆解 → 補充 → 反向擴充 → 連結的多輪迭代。&lt;/p>
&lt;hr>
&lt;h2 id="14-張卡片的拆解第一冷啟">14 張卡片的拆解（第一冷啟）&lt;/h2>
&lt;p>依 user 對 atomic 的標準（一卡一議題、一個議題多面向 OK、議題太多就拆），列出 10 張卡片提案：&lt;/p>
&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>#55 層錯位 / #56 視覺完成 ≠ 功能完成 / #57 三狀態區分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>指令澄清&lt;/td>
 &lt;td>#58 篩選類指令的澄清時機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>解法策略&lt;/td>
 &lt;td>#59 五策略對照 + #60-62 三張 pattern 卡（自動續抓 / 推進 query / 誠實 UX）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>抽象原則&lt;/td>
 &lt;td>#63 資料源形狀 / #64 同層合成&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>冷啟版本一次寫完不求完美 — 約 1700 行、各卡 self-contained。&lt;/p>
&lt;hr>
&lt;h2 id="七輪迭代">七輪迭代&lt;/h2>
&lt;h3 id="迭代-1抽-pattern--瘦身">迭代 1：抽 Pattern + 瘦身&lt;/h3>
&lt;p>寫完 #59 五策略後、發現 A/B/C/D/E 中 C（多 index）、E（明示縮小）沒對應 pattern 卡。抽出 #65 / #66 補完 pattern 卡組。同時瘦身 #59 → 純路由（細節留 pattern 卡）、#55 + #57 移除跟 #63 重複的「四類資料源」段。&lt;/p></description><content:encoded><![CDATA[<h2 id="這篇要說什麼">這篇要說什麼</h2>
<p><code>content/report/</code> 累積了 70+ 張原子化事後檢討卡片、<code>.claude/skills/</code> 收錄三個 protocol skill。這些是用來指導下一輪實作、又會被下一輪實作的學習回流修正的活基礎建設。</p>
<p>本文把這套系統實際跑一輪的歷程紀錄下來、當未來「想用這套系統的人」的 onboarding case study。主軸是修一個 search filter bug — 看似一週工作、實際走完八輪迭代、產出 14 張新卡片 + 兩個 skill 的 v0.2 + 4 個 CI test、過程中還抓到自己的 dogfooding 失敗、回頭修一次。</p>
<hr>
<h2 id="起點使用者問題">起點：使用者問題</h2>
<p>&ldquo;我們搜尋頁的 標題/內文篩選功能現在雖然做出來了、但是還是有一個很嚴重的 BUG&rdquo;</p>
<p>具體：Pagefind 分批 load、view 層 post-filter；切到 title-only 後、第二批 load more 的 8 筆全部 title 不含 query → 全 hidden、畫面閃但內容沒變、使用者看到「load more 沒效果」silent 失敗。</p>
<p>User 還明確補了一句：「<strong>所以除了用 JS 取巧解決畫面、但是實際功能面上怎麼配合跟實作 我們並沒有解決</strong>」— 這已經點到核心：問題不在畫面、在抽象層。</p>
<hr>
<h2 id="第一輪拆卡片之前先想清楚">第一輪：拆卡片之前先想清楚</h2>
<p>直接修 bug 是可選但不是 user 要的。User 強調：「<strong>先思考我的需求、然後思考各種狀況的邊界</strong>」。</p>
<p>依當時的兩個 skill — <code>requirement-protocol</code>（對話協議）跟 <code>frontend-with-playwright</code>（前端執行協議）— 把問題分解：</p>
<ol>
<li><strong>Bug 的結構性根因</strong>：filter 寫在視覺層、source 在資料層分批、兩層的「一筆」定義不一致 → silent 缺口</li>
<li><strong>解法策略空間</strong>：5 個合理選項（推進 query / 自動續抓 / 多 index / 誠實 UX / 明示縮小）— 每個機會成本不同</li>
<li><strong>跨領域通用性</strong>：這結構不只前端有 — 後端 middleware filter、map-reduce、SQL view 都同模式</li>
</ol>
<p>User 的關鍵回應：「<strong>這部份可以補充 SKILL 中演算法不足的原因 &hellip; 卡片是經過多次迭代、擴充、然後分拆、再擴充、最後做連結</strong>」。</p>
<p>明確了協作方式：先建卡片、再灌進 skill、最後才修。卡片本身要走原子化拆解 → 補充 → 反向擴充 → 連結的多輪迭代。</p>
<hr>
<h2 id="14-張卡片的拆解第一冷啟">14 張卡片的拆解（第一冷啟）</h2>
<p>依 user 對 atomic 的標準（一卡一議題、一個議題多面向 OK、議題太多就拆），列出 10 張卡片提案：</p>
<table>
  <thead>
      <tr>
          <th>分組</th>
          <th>卡片</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>問題分析</td>
          <td>#55 層錯位 / #56 視覺完成 ≠ 功能完成 / #57 三狀態區分</td>
      </tr>
      <tr>
          <td>指令澄清</td>
          <td>#58 篩選類指令的澄清時機</td>
      </tr>
      <tr>
          <td>解法策略</td>
          <td>#59 五策略對照 + #60-62 三張 pattern 卡（自動續抓 / 推進 query / 誠實 UX）</td>
      </tr>
      <tr>
          <td>抽象原則</td>
          <td>#63 資料源形狀 / #64 同層合成</td>
      </tr>
  </tbody>
</table>
<p>冷啟版本一次寫完不求完美 — 約 1700 行、各卡 self-contained。</p>
<hr>
<h2 id="七輪迭代">七輪迭代</h2>
<h3 id="迭代-1抽-pattern--瘦身">迭代 1：抽 Pattern + 瘦身</h3>
<p>寫完 #59 五策略後、發現 A/B/C/D/E 中 C（多 index）、E（明示縮小）沒對應 pattern 卡。抽出 #65 / #66 補完 pattern 卡組。同時瘦身 #59 → 純路由（細節留 pattern 卡）、#55 + #57 移除跟 #63 重複的「四類資料源」段。</p>
<h3 id="迭代-2補概念深度">迭代 2：補概念深度</h3>
<p>回頭讀 #56 / #63 / #64、補抽象層的「為什麼」：</p>
<ul>
<li>#56 加「驗收的時間軸：四個 checkpoint」概念</li>
<li>#63 加「形狀識別 protocol」+「形狀混合」+「形狀的可改造性」</li>
<li>#64 加「跨領域通用的本質 = 資訊可見範圍」+「上推代價」</li>
</ul>
<h3 id="迭代-3跨卡連結">迭代 3：跨卡連結</h3>
<p>新卡跟 #1-#54 既有卡互相補連結。例如 #55 ↔ #11 playwright、#57 ↔ #38 aria-live、#58 ↔ #21 decide-vs-confirm、#64 ↔ #43 minimum-scope + #44 SSOT。整個 collection 從兩個獨立輪次變一張互連網。</p>
<h3 id="迭代-4抽更高層原則">迭代 4：抽更高層原則</h3>
<p>重讀新卡發現兩個議題夠 abstract、值得抽獨立卡：</p>
<ul>
<li><strong>#67 寫作便利度跟意圖對齊反相關</strong> — 從「為什麼層錯位 bug 容易寫出來」抽出。發現它是 #43 / #44 / #45 / #64 的共同上位原則：<strong>便利位置 vs 對齊位置永遠反相關</strong></li>
<li><strong>#68 驗收的時間軸：四個 checkpoint</strong> — 從 #56 抽出獨立成卡</li>
</ul>
<h3 id="迭代-5跨輪共骨">迭代 5：跨輪共骨</h3>
<p>系統性掃 #1-#54 找跟新系列共骨的、加連結。例：#6 filter-order ↔ #58 / #59、#10 placeholder ↔ #68、#15 layout-test ↔ #68、#14 selector / #20 failure / #28 class-toggle ↔ #67。</p>
<h3 id="迭代-66768-加深">迭代 6：#67/#68 加深</h3>
<p>再讀兩張抽象卡、補「為什麼人會違反這條規則」的結構性解釋：</p>
<ul>
<li>#67 加「便利度的時間維度：當下便利 vs 未來便利反向」+「我等下會 refactor 是個謊言」</li>
<li>#68 加「為什麼 Ship 前 checkpoint 最常被跳過」（沒便利路徑）+「瀑布原則：漏一層代價指數放大」</li>
</ul>
<p>從「規則陳述」進到「結構性解釋」 — 不只說「該怎麼做」、也說「為什麼人會違反」。</p>
<h3 id="迭代-7compositional-writing-規範稽核">迭代 7：compositional-writing 規範稽核</h3>
<p>User 提醒「再做一次 compositional-writing 的檢查」。發現兩類違規：</p>
<ol>
<li><strong>Rule 7 違規</strong>：26 處「X 才合理的情境：實務上幾乎不存在」假反模式 — 改成「X 是反模式：理由」格式</li>
<li><strong>結構違規</strong>：#67/#68 是抽象層原則卡、不該寫設計取捨 ABCD（情境檢討卡的格式）— 改成「不該套用本原則的情境」（適用邊界）</li>
</ol>
<p>修完 31 張卡片（含既有 #1-#54）。整個 collection 對齊 v0.6 規範。</p>
<hr>
<h2 id="灌進-skills">灌進 Skills</h2>
<p>把 #55-#68 系列接進兩個 skill：</p>
<ul>
<li><strong>requirement-protocol v0.2</strong>：clarifying-ambiguous-instructions 加第 5 類「篩選類」+ 三問模板（呼應 #58）；SKILL.md 加「相關抽象層原則」段路由 #42-45 + #67-68</li>
<li><strong>frontend-with-playwright v0.2</strong>：新增第 7 份 reference <code>data-flow-and-filter-composition</code>（涵蓋 #55-#66 跨領域範例）；強調「不只前端、適用後端 / 演算法 / DB」</li>
</ul>
<p>Skill 的角色 = 路由器、Reports = 深度內容 — 兩層分工不重述。</p>
<hr>
<h2 id="實作策略-c--phase-1-4">實作：策略 C + Phase 1-4</h2>
<p>依 #59 + Pagefind 1.5.2 capabilities：</p>
<ul>
<li><strong>A 推進 query</strong>：不可行（Pagefind 無 native title filter API）</li>
<li><strong>C 多 index</strong>：採用（最對齊意圖）</li>
<li>B / D / E 是 fallback</li>
</ul>
<p>Phase 1-4：</p>
<ol>
<li>Makefile 跑 3 輪 pagefind（all / title / content）</li>
<li>single.html <code>&lt;content&gt;</code> → <code>&lt;div class=&quot;article-body&quot; data-pagefind-body&gt;</code></li>
<li>search.html 移除 view 層 post-filter、改 destroy + new PagefindUI(bundlePath)</li>
<li>4 個 Playwright tests 固化</li>
</ol>
<p>跑出來：<code>make site</code> 三 index 成功、<code>make test</code> 4/4 PASS、live 驗證 sparse case 顯示 explicit empty。<strong>看起來完工</strong>。</p>
<hr>
<h2 id="user-抓到-dogfooding-失敗--第-8-輪">User 抓到 dogfooding 失敗 — 第 8 輪</h2>
<p>User 問：「<strong>剛剛的過程我不確定、你開始修改之前有先寫測試確保符合預測狀態、然後才調整嗎？</strong>」</p>
<p>沒有。流程是：先修 → 才補測試 → 4/4 GREEN。<strong>沒走 RED</strong>。</p>
<p>這是 #67「便利驅動」+ #68「Checkpoint 2/3 內部協議」的 dogfooding 失敗。我寫了 #67/#68 教這些原則、自己卻違反。</p>
<p>依 user 規範：先建卡片再修。抽 <strong>#69 Test-First：先看到 RED 才相信 GREEN</strong>：</p>
<ul>
<li>測試本身是程式、會有 bug（5 種失敗模式）</li>
<li>沒看過 RED = 不知道測試有沒有 catch 能力</li>
<li>RED → GREEN 兩個訊號都看到 = 測試 + 修復都被驗證</li>
</ul>
<p>retrospective 補驗證流程：checkout pre-fix commit → cherry-pick test → build → run（看 RED）→ restore → run（看 GREEN）。</p>
<p>跑下去 — 結果震撼：<strong>4 個測試只有 1 個真的 catch 到 bug、其他 3 個對 buggy code 也 PASS</strong>（placebo）。如果不做 retrospective、會帶著 3/4 placebo 測試 ship。</p>
<p>強化測試（network-level + structural assertion 替換弱 invariant）：buggy code 1 PASS / 3 FAIL、fixed code 4 PASS。RED-GREEN 真的 catch 到 bug + 真的解掉。</p>
<hr>
<h2 id="user-抓到第二個-dogfooding-失敗--checkpoint-1">User 抓到第二個 dogfooding 失敗 — Checkpoint 1</h2>
<p>我問 user 還有什麼該迭代。User 列了 7 項、選 1+2：</p>
<ol>
<li>補 Checkpoint 1（列使用者意圖完整集）</li>
<li>跟 user 確認 known limitations</li>
</ol>
<p>跑 Checkpoint 1 retrospective — 用 Playwright MCP 系統性測 5 維度（data / interaction / URL / a11y / performance）。發現 3 個 silent 缺口：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>漏掉的 case</th>
          <th>結論</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>URL state</td>
          <td><code>?q=X&amp;scope=Y</code> 持久化</td>
          <td>完全沒實作</td>
      </tr>
      <tr>
          <td>A11y</td>
          <td>Tab order: scope 在 search input 之前</td>
          <td>反 mental model</td>
      </tr>
      <tr>
          <td>Filter UX</td>
          <td>type/tag filter 在 sub-mode 完全消失</td>
          <td>Silent 限制</td>
      </tr>
  </tbody>
</table>
<p>依 user 規範：<strong>先建卡片再修</strong>。抽：</p>
<ul>
<li><strong>#70 URL 是 stateful UI 的儲存層</strong> — 5 個儲存層特性對照 + 三問判準</li>
<li><strong>#71 Tab Order = DOM Order = Mental Model 三者對齊</strong> — DOM 順序 = tab 順序、不對齊時優先重排 DOM</li>
<li>更新 #68 加「為什麼 Checkpoint 1 也常被跳過」段、用本次任務當 self-case</li>
</ul>
<p>然後實作 — 依 #69 RED-GREEN 順序：</p>
<ol>
<li>寫 4 個 RED tests</li>
<li>跑 → 4 個 fail（confirms RED）</li>
<li>修 search.html（URL persist + DOM reorder + UI hint）</li>
<li>跑 → 8/8 GREEN</li>
</ol>
<hr>
<h2 id="ci--自動化">CI + 自動化</h2>
<p>最後補 CI 防護：</p>
<ul>
<li><strong><code>.github/workflows/playwright.yml</code></strong> — push / PR 自動跑 8 個 tests</li>
<li><strong><code>deploy.yml</code> 修 critical bug</strong> — production 一直只 build 單 index、現在 build 三份對齊本地</li>
<li><strong><code>make test</code> + <code>make verify-red-green PRE_FIX=&lt;sha&gt;</code></strong> — codify retrospective 流程、不需手動 stash / checkout / restore</li>
</ul>
<hr>
<h2 id="數字總結">數字總結</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Commits</td>
          <td>30+</td>
      </tr>
      <tr>
          <td>新卡片</td>
          <td>17（#55-#71）</td>
      </tr>
      <tr>
          <td>既有卡修改</td>
          <td>31 張（rule 7 稽核）</td>
      </tr>
      <tr>
          <td>新 skill reference</td>
          <td>1（data-flow-and-filter-composition）</td>
      </tr>
      <tr>
          <td>Skill 版本</td>
          <td>requirement-protocol v0.1 → v0.2、frontend-with-playwright v0.1 → v0.2</td>
      </tr>
      <tr>
          <td>Playwright tests</td>
          <td>8</td>
      </tr>
      <tr>
          <td>RED-GREEN cycles</td>
          <td>2（初版測試 + 強化版）</td>
      </tr>
      <tr>
          <td>CI workflows 加 / 修</td>
          <td>2（新增 playwright + 修 deploy multi-index）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="學到什麼">學到什麼</h2>
<h3 id="1-cards-skills-系統是雙向的">1. Cards-skills 系統是雙向的</h3>
<p>不是「先寫卡片、再用卡片」。是「卡片指導實作、實作問題回流卡片」。每一輪迭代都把學到的東西反饋。本次 14 張新卡有 8 張是修過程中實際遇到的問題抽出來的、不是預先想的。</p>
<h3 id="2-user-提問是外部觸發">2. User 提問是「外部觸發」</h3>
<p>我自己跑 #67 / #68 / Checkpoint 1 的機率低 — 因為這些都是「沒便利路徑」的工作。User 的兩次提問（「有先寫測試嗎」+「需求確認最重要功能」）剛好對應 #69 + Checkpoint 1 的觸發。<strong>結構性偏差需要外部觸發來修正、不能靠自我提醒</strong>。</p>
<h3 id="3-test-過--對齊使用者意圖">3. Test 過 ≠ 對齊使用者意圖</h3>
<p>第一輪修完、跑 4/4 GREEN、看起來完工。實際漏了：</p>
<ul>
<li>3 個測試是 placebo（沒做 RED 不知道）</li>
<li>3 個 silent 缺口（沒做 Checkpoint 1 不知道）</li>
</ul>
<p>任何「跑得通就 OK」的訊號都低資訊量。Real 訊號 = 對照「使用者意圖完整集合」逐一驗收。</p>
<h3 id="4-一個-bug-修完--一個-case-study-起點">4. 一個 bug 修完 = 一個 case study 起點</h3>
<p>如果停在「bug 修了、test 過了」、這次任務 5 個 commits 結束。User 的兩次提問把它變成 30+ 個 commits 的 case study、產出 17 張新卡 + 兩個 skill 升級 + CI 補強。<strong>修 bug 是 trigger、不是終點</strong>。</p>
<hr>
<h2 id="適合-reuse-這個流程的條件">適合 reuse 這個流程的條件</h2>
<p>不是每個 bug 都該走這套。適合的訊號：</p>
<ul>
<li>Bug 修法不直觀、會碰到多種策略選項（→ 需要 #59 類取捨架構）</li>
<li>修法可能影響其他 feature 或產生新案例（→ 需要 Checkpoint 1）</li>
<li>需要長期 regression 防護（→ 需要 #69 RED-GREEN 驗證）</li>
<li>修的過程中發現新原則（→ 抽卡片）</li>
</ul>
<p>不適合：純 typo / config / build 失敗 — 直接修。</p>
<hr>
<h2 id="對未來想用這套系統的人">對未來想用這套系統的人</h2>
<p>進入點：</p>
<ol>
<li>讀 <code>content/skills/_index.md</code> — 三個 skill 的 routing table</li>
<li>從你的問題情境找對應 skill：
<ul>
<li>不確定怎麼跟 user 溝通 → <code>requirement-protocol</code></li>
<li>前端 / 資料流實作 → <code>frontend-with-playwright</code></li>
<li>寫文件 / 註解 / log → <code>compositional-writing</code></li>
</ul>
</li>
<li>Skill 路由你到 specific reference、reference 路由你到 <code>content/report/</code> 深度卡片</li>
<li>修問題過程中發現新原則 → 抽卡片回流</li>
</ol>
<p>「卡片不是在實作之前一次寫完、是在實作之中持續累積」 — 這套系統的 leverage 在於「下一個類似問題能直接用、不用重新發明」。</p>
<hr>
<h2 id="結語">結語</h2>
<p><code>content/report/</code> 從 54 張長到 71 張、<code>.claude/skills/</code> 從 v0.1 進到 v0.2、CI 從假 pass 變真防護、search bug 從 silent 失敗變到 8/8 regression test 守護。</p>
<p>過程不是線性。是「先做 → 抓到 dogfooding 失敗 → 抽卡片 → 回頭修 → 再被抓失敗 → 再抽卡片 → 再修」。每一輪都讓系統往對齊使用者意圖的方向多走一點。</p>
<p>User 的角色關鍵：兩次提問都不在「指出 bug」、是在「指出我跳過的 checkpoint」。這是純執行者看不到的盲點 — 自己的 dogfooding 失敗。<strong>外部 reviewer 是 cards-skills 系統的必要組件、不是 optional</strong>。</p>
<p>下次有類似情境的人 — 不需要把這條路再走一遍、直接用 #55-#71 + 三個 skill 起步。如果發現新 case、抽新卡回流。系統的價值在每次使用都會變強。</p>
]]></content:encoded></item><item><title>決策對話協議的浮現：從 #74 到 #81 的多層迭代</title><link>https://tarrragon.github.io/blog/posts/%E6%B1%BA%E7%AD%96%E5%B0%8D%E8%A9%B1%E5%8D%94%E8%AD%B0%E7%9A%84%E6%B5%AE%E7%8F%BE%E5%BE%9E-%2374-%E5%88%B0-%2381-%E7%9A%84%E5%A4%9A%E5%B1%A4%E8%BF%AD%E4%BB%A3/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/posts/%E6%B1%BA%E7%AD%96%E5%B0%8D%E8%A9%B1%E5%8D%94%E8%AD%B0%E7%9A%84%E6%B5%AE%E7%8F%BE%E5%BE%9E-%2374-%E5%88%B0-%2381-%E7%9A%84%E5%A4%9A%E5%B1%A4%E8%BF%AD%E4%BB%A3/</guid><description>&lt;h2 id="這篇要說什麼">這篇要說什麼&lt;/h2>
&lt;p>&lt;a href="../cards-skills-system-case-study/">前一篇 case study&lt;/a> 紀錄的是「實作驅動」的閉環 — 從一個 bug 出發、逼出新卡片。&lt;/p>
&lt;p>本篇紀錄的是 &lt;strong>「對話驅動」的閉環&lt;/strong> — 不修任何 production code、純粹從對話中浮現新卡。觸發點是 user 的一句反思：「&lt;strong>剛剛提出很多不同方向的決策做選擇、這些選擇應該被做成卡片然後分析或者分拆細節研究&lt;/strong>」。&lt;/p>
&lt;p>接下來六輪 spiral 迭代、產生 8 張新卡（&lt;a href="https://tarrragon.github.io/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">#74-#81&lt;/a>）+ 1 份 SKILL reference + skill v0.5。本文紀錄這條路徑、當作 &lt;a href="https://tarrragon.github.io/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 卡片系統的迭代浮現&lt;/a> 的具體實例。&lt;/p>
&lt;hr>
&lt;h2 id="起點對話中的反思訊號">起點：對話中的反思訊號&lt;/h2>
&lt;p>對話到第 N 回合時、agent 已經在多次出現「決策呈現」的場景：&lt;/p>
&lt;ul>
&lt;li>「Content mode 三選一」 → user 答 (a)&lt;/li>
&lt;li>「一次 ship 全部 vs 分批」 → user 答「一次」&lt;/li>
&lt;li>「五策略選一」 → user 答 「C 主 + D 補」&lt;/li>
&lt;li>「ship D 還是 B/C」 → user 答 「先 D、B/C 下輪」&lt;/li>
&lt;li>「反省選哪幾個」 → user 答 「1+2」&lt;/li>
&lt;/ul>
&lt;p>每次 agent 都呈現得不一樣、user 也每次回得不一樣。&lt;strong>反覆出現但形式各異 = 抽 meta 的訊號&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 四篇實作的共同抽象。">#42 2 次門檻&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 迭代浮現&lt;/a>）。&lt;/p>
&lt;p>User 的「&lt;strong>這些選擇應該被做成卡片&lt;/strong>」就是 meta-訊號的明確化 — 不是 agent 自己浮現的、是 user 點出來的。&lt;strong>External trigger（&lt;a href="https://tarrragon.github.io/blog/report/external-trigger-for-high-roi-work/" data-link-title="高 ROI 無外部觸發的工作會被結構性跳過" data-link-desc="工作有兩個獨立維度：ROI 高低 &amp;#43; 是否有外部觸發。高 ROI &amp;#43; 無觸發 = ROI 的承諾、拖延的現實。靠紀律不可行 — 結構性偏差需要結構性對策（外部觸發 / CI / hook / 排程 / pair）。本卡是 #67 便利反相關、#68 checkpoint 跳過、#69 RED 跳過的共同上位原則。">#72&lt;/a>）才能逼出抽 meta 這個高 ROI 但無觸發的工作&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<h2 id="這篇要說什麼">這篇要說什麼</h2>
<p><a href="../cards-skills-system-case-study/">前一篇 case study</a> 紀錄的是「實作驅動」的閉環 — 從一個 bug 出發、逼出新卡片。</p>
<p>本篇紀錄的是 <strong>「對話驅動」的閉環</strong> — 不修任何 production code、純粹從對話中浮現新卡。觸發點是 user 的一句反思：「<strong>剛剛提出很多不同方向的決策做選擇、這些選擇應該被做成卡片然後分析或者分拆細節研究</strong>」。</p>
<p>接下來六輪 spiral 迭代、產生 8 張新卡（<a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">#74-#81</a>）+ 1 份 SKILL reference + skill v0.5。本文紀錄這條路徑、當作 <a href="/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 卡片系統的迭代浮現</a> 的具體實例。</p>
<hr>
<h2 id="起點對話中的反思訊號">起點：對話中的反思訊號</h2>
<p>對話到第 N 回合時、agent 已經在多次出現「決策呈現」的場景：</p>
<ul>
<li>「Content mode 三選一」 → user 答 (a)</li>
<li>「一次 ship 全部 vs 分批」 → user 答「一次」</li>
<li>「五策略選一」 → user 答 「C 主 + D 補」</li>
<li>「ship D 還是 B/C」 → user 答 「先 D、B/C 下輪」</li>
<li>「反省選哪幾個」 → user 答 「1+2」</li>
</ul>
<p>每次 agent 都呈現得不一樣、user 也每次回得不一樣。<strong>反覆出現但形式各異 = 抽 meta 的訊號</strong>（<a href="/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">#42 2 次門檻</a> + <a href="/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 迭代浮現</a>）。</p>
<p>User 的「<strong>這些選擇應該被做成卡片</strong>」就是 meta-訊號的明確化 — 不是 agent 自己浮現的、是 user 點出來的。<strong>External trigger（<a href="/blog/report/external-trigger-for-high-roi-work/" data-link-title="高 ROI 無外部觸發的工作會被結構性跳過" data-link-desc="工作有兩個獨立維度：ROI 高低 &#43; 是否有外部觸發。高 ROI &#43; 無觸發 = ROI 的承諾、拖延的現實。靠紀律不可行 — 結構性偏差需要結構性對策（外部觸發 / CI / hook / 排程 / pair）。本卡是 #67 便利反相關、#68 checkpoint 跳過、#69 RED 跳過的共同上位原則。">#72</a>）才能逼出抽 meta 這個高 ROI 但無觸發的工作</strong>。</p>
<hr>
<h2 id="迭代過程六輪-spiral">迭代過程：六輪 spiral</h2>
<h3 id="輪-1列候選不寫卡">輪 1：列候選、不寫卡</h3>
<p>User 的「應該被做成卡片」沒指定要寫幾張、寫什麼。Agent 列五個候選（A-E）+ 推薦 B+C 組合。User 回「<strong>每個都做成卡片</strong>」+ 給出迭代原則：「<strong>先拓展知識庫、然後才整理成上層的抽象決策依據、最後才做決策</strong>」。</p>
<p>這個原則 = [#81] 的 process explicit form：先原子卡 → 後 meta-卡 → 最後決策（套用）。</p>
<h3 id="輪-2寫五張原子卡74-78">輪 2：寫五張原子卡（#74-#78）</h3>
<p>每張一個維度：</p>
<ul>
<li>#74 呈現格式（選項表 + 推薦 + 開放修改）</li>
<li>#75 主策略 + 補強疊加（不必互斥）</li>
<li>#76 分批 ship（三軸切分）</li>
<li>#77 「現在不決定」是合法選項</li>
<li>#78 反省任務預設複選</li>
</ul>
<p>寫的時候各自獨立、沒明確意識到「這五張其實是同一回事的五個面向」。<strong>在第 5 張寫到一半才開始覺得「好像每張都在打開一個固定的 default」</strong>。</p>
<p>訊號：[#81] 「寫第 N 張卡、發現大段內容跟前一張重複」 → 抽 meta。</p>
<h3 id="輪-3抽-meta-卡-79">輪 3：抽 meta-卡 #79</h3>
<p>User 接著問「都做、用多層迭代去拓展卡片」、agent 回應時自己浮現了 meta-發現：<strong>這五張其實對應五個獨立維度、可組合成 2^5 = 32 種對話形態</strong>。</p>
<p>寫 #79 時、把五張卡的「打開的 default」歸納成五個維度 + 五步判讀。<strong>Meta-卡讓五張原子卡從「平行五張」變成「有結構的網」</strong>。</p>
<p>回頭給每張原子卡補上「跟 #79 的對應」 cross-link、迭代結束。</p>
<h3 id="輪-4沉澱成-reference">輪 4：沉澱成 reference</h3>
<p>光有卡還不夠 — 實作中要翻 5 張卡才能完整 apply、太貴。</p>
<p>寫 <code>references/decision-dialogue.md</code>、把五步判讀 + 完整模板 + self-check 沉澱成一份可直接套用的 protocol。同時更新 SKILL.md 加 trigger route（「呈現決策 / 開放問 / 反省題」）+ Directory Index + 抽象層原則段。</p>
<p>訊號：[#81] 「實作中要回查 ≥ 3 張卡」 → 沉澱 reference。</p>
<h3 id="輪-5dogfood--反向補卡80-81">輪 5：dogfood + 反向補卡（#80-#81）</h3>
<p>User 的「我們想得到的都作、直到推演到極限」逼 agent 自查：</p>
<p><strong>自查 1</strong>：回頭看 agent 在這輪對話的回應、找 collapse 反模式。發現 4 處：</p>
<ul>
<li>「需要我繼續嗎？」 = yes/no（最隱形的 collapse）</li>
<li>「下一層候選」用 bullet 沒適配欄</li>
<li>推薦騎牆「A 比較好不過 B 也行」</li>
<li>反省題列點未明示「互不衝突」</li>
</ul>
<p>→ 寫 #80 Yes/No 二選、把 dogfood 4 例寫進 reference 作為「Bad/Good 對照」。</p>
<p><strong>自查 2</strong>：「這套迭代過程本身是不是 cardable？」是 — 寫 #81 卡片系統的迭代浮現、紀錄「原子 → meta → reference」的 spiral 結構。</p>
<p>訊號：[#81] 「meta-卡寫太早、新 case 一直破壞」的反面 — 寫得剛好、反而能容納新 case（#80、#81 自己）。</p>
<h3 id="輪-6跨連--補強">輪 6：跨連 + 補強</h3>
<p>把 #75（主+補強疊加）展開到 selector pattern：[#46-#49] 看似互斥（每個元件選一個起點）、實際在同一份 code 內可疊加（document + closest 共用）。<strong>Meta-原則的價值之一就是回頭發現舊卡之間有新關係</strong>。</p>
<p>更新 #59（五策略選擇矩陣）加「並用」段落、引用 #75 + #76。</p>
<hr>
<h2 id="過程中的觀察">過程中的觀察</h2>
<h3 id="1-user-的-prompt-直接決定-spiral-深度">1. User 的 prompt 直接決定 spiral 深度</h3>
<p>User 的三句話分別觸發三層深度：</p>
<table>
  <thead>
      <tr>
          <th>User 的話</th>
          <th>觸發深度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「應該被做成卡片」</td>
          <td>寫原子卡（layer 1）</td>
      </tr>
      <tr>
          <td>「先拓展知識庫、再整理上層、最後決策」</td>
          <td>抽 meta-卡（layer 2）+ reference（layer 3）</td>
      </tr>
      <tr>
          <td>「都作、推演到極限」</td>
          <td>dogfood + 反向補卡（layer 4-5）</td>
      </tr>
  </tbody>
</table>
<p>每句話都是 [#72] L4 外部觸發 — 沒這些話、agent 不會自己走到第 5 層。<strong>Spiral 深度由 trigger 決定、不由 agent 紀律決定</strong>。</p>
<h3 id="2-dogfood-回饋的-roi-比新卡高">2. Dogfood 回饋的 ROI 比新卡高</h3>
<p>#80（yes/no）的內容比 #74 短得多、但 ROI 可能更高 — 因為它捕捉的是「最常見、最隱形」的反模式。同樣 reference 的「dogfood Bad/Good 4 例」比抽象描述有用 — 將來 agent 看到自己寫類似格式、能直接認出來。</p>
<p>訊號：<strong>具體例子（特別是反例）的 ROI 通常 &gt; 抽象描述</strong>。</p>
<h3 id="3-meta-卡跟-reference-的職責不同">3. Meta-卡跟 reference 的職責不同</h3>
<p>寫完 #79 還不夠、需要 reference — 因為：</p>
<ul>
<li>卡片回答「為什麼」、reference 回答「怎麼做」</li>
<li>卡片是讀爽的、reference 是被翻的</li>
<li>卡片可選、reference 在實作中是 must</li>
</ul>
<p><strong>兩者缺一不可</strong>：只寫卡 → 知道但忘記用；只寫 reference → 知道做但不知道為什麼、難 maintain。</p>
<h3 id="4-真實的-spiral-不是線性">4. 真實的 spiral 不是線性</h3>
<p>寫 #74 時不知道有 #79、寫 #79 時回頭改 #74-#78、寫 reference 時又發現 #80 漏了、寫 #80 時補 reference 的 dogfood 段。<strong>每一層完成後都會反過來修上一層</strong>。</p>
<p>線性思維（「先寫完 layer 1 才寫 layer 2」）會卡住、spiral 思維（「來回修、每輪都加深」）才能浮現完整結構。</p>
<hr>
<h2 id="跟既有原則的關係">跟既有原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>既有原則</th>
          <th>在本次 spiral 中的角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/report/two-occurrence-threshold/" data-link-title="2 次門檻：第一次是運氣、第二次是訊號" data-link-desc="同一個問題出現第 2 次時、就該停下來把處理層級升一階 — 從推理升到量測、從手動驗證升到自動化、從同方向嘗試升到換思路。第 1 次失敗的資訊不足、第 2 次提供「重複出現」的證據、值得付出升級成本。本文是 #11 / #15 / #20 / #23 四篇實作的共同抽象。">#42 2 次門檻</a></td>
          <td>第 N 次出現決策呈現 = 抽 meta 的訊號</td>
      </tr>
      <tr>
          <td><a href="/blog/report/minimum-necessary-scope-is-sanity-defense/" data-link-title="最小必要範圍是 sanity 防線：保護行為可預測性" data-link-desc="縮 selector 範圍、observer 範圍、JS 操作範圍 — 不是為了效能、是為了讓行為可預測、不被未來變動打破。本文是 #13 / #14 / #29 三篇實作的共同抽象。">#43 最小必要範圍</a></td>
          <td>先窄後寬：原子卡（窄）→ meta（寬）、不要直接寫 meta</td>
      </tr>
      <tr>
          <td><a href="/blog/report/ease-of-writing-vs-intent-alignment/" data-link-title="寫作便利度跟意圖對齊反相關" data-link-desc="寫程式時最容易寫出的版本、通常是離意圖最遠的版本。便利度建立在「現有上下文 / 已 materialize 資料 / 已存在 API」上、而意圖對齊需要找到正確的層、處理上游、跨抽象層 — 兩者方向相反。識別這個反相關 = 識別自己掉進「容易寫的陷阱」。">#67 寫作便利度反相關</a></td>
          <td>「直接寫 meta」容易、「迭代浮現」難 — 真實結構不對齊容易寫的格式</td>
      </tr>
      <tr>
          <td><a href="/blog/report/external-trigger-for-high-roi-work/" data-link-title="高 ROI 無外部觸發的工作會被結構性跳過" data-link-desc="工作有兩個獨立維度：ROI 高低 &#43; 是否有外部觸發。高 ROI &#43; 無觸發 = ROI 的承諾、拖延的現實。靠紀律不可行 — 結構性偏差需要結構性對策（外部觸發 / CI / hook / 排程 / pair）。本卡是 #67 便利反相關、#68 checkpoint 跳過、#69 RED 跳過的共同上位原則。">#72 高 ROI 無觸發</a></td>
          <td>抽 meta + 寫 reference 沒外部觸發不會做、user 的話是 L4 觸發</td>
      </tr>
      <tr>
          <td><a href="/blog/report/decision-dialogue-dimensions/" data-link-title="決策對話的五個維度：保持完整選擇空間" data-link-desc="對話中的「決策」不是單一動作、是多維度選擇空間：呈現格式 / 策略疊加 / 批次邊界 / 時間軸 / 選項類型。預設多半 collapse 到最窄格（開放問 &#43; 單策略 &#43; 一次完成 &#43; 立刻決 &#43; 單選）、塞使用者進最少自由度的盒子。本卡是 #74-#78 的上層串連 — 五張卡各對應一個維度的鬆綁。">#79 決策對話的五維度</a></td>
          <td>本次 spiral 的 output、也是元素之一</td>
      </tr>
      <tr>
          <td><a href="/blog/report/cards-as-living-system-iteration/" data-link-title="卡片系統的迭代浮現：原子卡 → meta-卡 → reference 三層展開" data-link-desc="知識卡片系統不是一次寫成、是 dialogue → 原子卡 → meta-卡 → reference 的迭代浮現。每一輪迭代解決上一輪的 over-fit / under-fit、串連分散的卡片、抽出 meta-原則、最後沉澱成可直接套用的 reference 文件。本卡是 cards-skills 系統設計的 process-level 元原則。">#81 卡片系統的迭代浮現</a></td>
          <td>本次 spiral 的 process-level 抽象</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="本次-spiral-的-output-清單">本次 spiral 的 output 清單</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>數量</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>原子卡</td>
          <td>5</td>
          <td>#74-#78（呈現 / 疊加 / 分批 / 延後 / 複選）</td>
      </tr>
      <tr>
          <td>Meta-卡</td>
          <td>1</td>
          <td>#79 五維度</td>
      </tr>
      <tr>
          <td>反向補卡</td>
          <td>2</td>
          <td>#80 yes/no、#81 迭代浮現</td>
      </tr>
      <tr>
          <td>Reference</td>
          <td>1</td>
          <td><code>decision-dialogue.md</code>（runtime + blog）</td>
      </tr>
      <tr>
          <td>Skill 整合</td>
          <td>2</td>
          <td>requirement-protocol v0.5、frontend-with-playwright v0.4</td>
      </tr>
      <tr>
          <td>跨連</td>
          <td>多處</td>
          <td>#59 加疊加段、#46-#49 加 #75 跨連</td>
      </tr>
      <tr>
          <td>Case study</td>
          <td>1</td>
          <td>本文</td>
      </tr>
  </tbody>
</table>
<p><strong>整輪迭代的成本</strong>：純對話、無 production code 改動、無新測試。<strong>整輪迭代的價值</strong>：未來 agent 在每次「決策呈現」場景都有 reference 可翻、有 self-check 可用、有 dogfood 例子可對照。</p>
<hr>
<h2 id="結語">結語</h2>
<p>本系統的成型不是「用心寫文件」、是接受**「對話會浮現結構、原子卡會自我串連、meta-卡會回頭修原子卡」這個 spiral 真相**、然後讓每輪迭代都加深一點。</p>
<p>下一次 user 在對話中又出現「這個應該被做成卡片」訊號時、流程已經是現成的 — 套 [#81] 的三層展開 + [#72] 的 L4 觸發、就能繼續長新卡。<strong>真正的 knowledge infrastructure 不是寫一次的文件、是長期 spiral 的 living system</strong>。</p>
]]></content:encoded></item><item><title>8.0 Go 的選型案例總覽</title><link>https://tarrragon.github.io/blog/go/08-case-studies/selection-patterns/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/selection-patterns/</guid><description>&lt;p>Go 選型案例的核心用途是把語言特性對回服務壓力。公司案例提供的價值通常來自三件事：服務遇到什麼壓力、Go 解決哪一段工程問題、團隊因此得到什麼維護收益。&lt;/p>
&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>Google、Microsoft、CloudWeGo&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高併發即時服務&lt;/td>
 &lt;td>長連線、低延遲、client 數量大&lt;/td>
 &lt;td>Twitch、Stream、Cloudflare&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>效能敏感遷移&lt;/td>
 &lt;td>既有系統已有瓶頸，局部元件需要更穩定的效能&lt;/td>
 &lt;td>Dropbox、PayPal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分散式基礎設施&lt;/td>
 &lt;td>一致性、複製、排程、網路協調是核心問題&lt;/td>
 &lt;td>Cockroach Labs、Kubernetes 生態&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="大規模平台服務先看團隊是否需要一致的服務形狀">大規模平台服務：先看團隊是否需要一致的服務形狀&lt;/h3>
&lt;p>大規模平台服務的核心訊號是「很多服務需要用相近方式開發、部署與維護」。例如一組雲端基礎設施服務需要共用 HTTP &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/health-check-liveness/" data-link-title="Liveness" data-link-desc="說明平台如何判斷 process 是否仍然存活，以及何時應重啟">health check&lt;/a>、structured &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log&lt;/a>、configuration、context cancellation 與單一 binary 部署流程。Go 的價值在於讓服務骨架簡單、依賴明確，讓不同團隊看到相似的程式入口與 package 結構。&lt;/p>
&lt;p>這類案例可以回到 &lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/simplicity/" data-link-title="0.1 Go 的簡單哲學與認知負擔" data-link-desc="理解 Go 為什麼偏好顯式、直線流程與少量語法">Go 的簡單哲學與認知負擔&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/composition-root/" data-link-title="7.7 composition root 與依賴組裝" data-link-desc="把具體 adapter、config 與 usecase wiring 留在應用入口層">composition root 與依賴組裝&lt;/a> 對照。&lt;/p>
&lt;h3 id="高併發即時服務先看連線與事件是否長時間存在">高併發即時服務：先看連線與事件是否長時間存在&lt;/h3>
&lt;p>高併發即時服務的核心訊號是「server 需要同時管理大量仍然在線的工作」。聊天室、即時通知、直播狀態、代理服務與邊緣網路服務，都可能同時面對大量 connection、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a> 與 cleanup。Go 的 goroutine、channel、context 與標準網路庫讓這些生命週期可以直接寫在程式裡。&lt;/p>
&lt;p>這類案例可以回到 &lt;a href="https://tarrragon.github.io/blog/go/04-concurrency/goroutine/" data-link-title="4.1 goroutine：輕量並發工作" data-link-desc="用 goroutine 啟動並發工作，並設計清楚的退出條件">goroutine：背景工作與服務生命週期&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/04-concurrency/channel/" data-link-title="4.2 channel：資料傳遞與 backpressure " data-link-desc="理解 channel 如何在 goroutine 之間傳遞資料並形成 backpressure ">channel：事件流與 backpressure &lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構&lt;/a> 對照。&lt;/p>
&lt;h3 id="效能敏感遷移先看瓶頸是否集中在清楚邊界">效能敏感遷移：先看瓶頸是否集中在清楚邊界&lt;/h3>
&lt;p>效能敏感遷移的核心訊號是「整個產品仍可沿用原本架構，但某段服務已經成為穩定性或成本瓶頸」。例如檔案同步、資料轉換、API gateway、build pipeline 或推送服務。這時 Go 常作為局部重寫選項，讓瓶頸元件取得更好的 CPU、memory、部署與並發表現。&lt;/p>
&lt;p>這類案例可以回到 &lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/concurrency-language-position/" data-link-title="0.5 Go 和其他並發語言的差異" data-link-desc="比較 Go、Java、C#、Rust、Node.js、Python async、Erlang/Elixir 在並發服務中的工程定位">Go 和其他並發語言的差異&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/03-runtime-profiling/" data-link-title="模組三：Runtime 與效能診斷" data-link-desc="GC、memory limit、pprof、goroutine leak 與 allocation 壓力">Runtime 與效能診斷&lt;/a> 對照。&lt;/p>
&lt;h3 id="分散式基礎設施先看主要問題是否在協調與可靠性">分散式基礎設施：先看主要問題是否在協調與可靠性&lt;/h3>
&lt;p>分散式基礎設施的核心訊號是「系統價值來自多節點協調」。資料庫、排程器、服務治理框架與網路控制平面，都需要清楚處理 context、retry、timeout、狀態同步與觀測訊號。Go 在這裡的價值通常是簡單語法、明確錯誤路徑、標準工具鏈與可讀的並發模型。&lt;/p>
&lt;p>這類案例可以回到 &lt;a href="https://tarrragon.github.io/blog/go-advanced/04-architecture-boundaries/" data-link-title="模組四：架構邊界與事件系統" data-link-desc="用事件驅動架構拆解事件來源、處理流程、狀態邊界與即時推送">架構邊界與事件系統&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/07-distributed-operations/" data-link-title="模組七：跨節點與平台整合" data-link-desc="把單一 Go 服務延伸到資料庫、queue、跨節點 WebSocket、可觀測性與部署平台">跨節點與平台整合&lt;/a> 對照。&lt;/p>
&lt;h2 id="閱讀案例的判斷順序">閱讀案例的判斷順序&lt;/h2>
&lt;ol>
&lt;li>先找服務壓力：併發、部署、效能、協調或長期維護。&lt;/li>
&lt;li>再找 Go 的切入點：goroutine、標準庫、單一 binary、型別與 package 邊界。&lt;/li>
&lt;li>最後回到章節：把案例對應到前面已學過的 Go 概念。&lt;/li>
&lt;/ol>
&lt;p>案例閱讀的重點是建立選型判斷，而非模仿公司規模。小型服務也可能遇到長連線、背景 worker 或部署簡化問題；大型公司案例只是把這些壓力放大到更容易觀察。&lt;/p></description><content:encoded><![CDATA[<p>Go 選型案例的核心用途是把語言特性對回服務壓力。公司案例提供的價值通常來自三件事：服務遇到什麼壓力、Go 解決哪一段工程問題、團隊因此得到什麼維護收益。</p>
<h2 id="案例類型總覽">案例類型總覽</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>觀察訊號</th>
          <th>代表案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大規模平台服務</td>
          <td>服務多、部署頻繁、依賴邊界需要清楚</td>
          <td>Google、Microsoft、CloudWeGo</td>
      </tr>
      <tr>
          <td>高併發即時服務</td>
          <td>長連線、低延遲、client 數量大</td>
          <td>Twitch、Stream、Cloudflare</td>
      </tr>
      <tr>
          <td>效能敏感遷移</td>
          <td>既有系統已有瓶頸，局部元件需要更穩定的效能</td>
          <td>Dropbox、PayPal</td>
      </tr>
      <tr>
          <td>分散式基礎設施</td>
          <td>一致性、複製、排程、網路協調是核心問題</td>
          <td>Cockroach Labs、Kubernetes 生態</td>
      </tr>
  </tbody>
</table>
<h3 id="大規模平台服務先看團隊是否需要一致的服務形狀">大規模平台服務：先看團隊是否需要一致的服務形狀</h3>
<p>大規模平台服務的核心訊號是「很多服務需要用相近方式開發、部署與維護」。例如一組雲端基礎設施服務需要共用 HTTP <a href="/blog/backend/knowledge-cards/health-check-liveness/" data-link-title="Liveness" data-link-desc="說明平台如何判斷 process 是否仍然存活，以及何時應重啟">health check</a>、structured <a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a>、configuration、context cancellation 與單一 binary 部署流程。Go 的價值在於讓服務骨架簡單、依賴明確，讓不同團隊看到相似的程式入口與 package 結構。</p>
<p>這類案例可以回到 <a href="/blog/go/00-philosophy/simplicity/" data-link-title="0.1 Go 的簡單哲學與認知負擔" data-link-desc="理解 Go 為什麼偏好顯式、直線流程與少量語法">Go 的簡單哲學與認知負擔</a>、<a href="/blog/go/07-refactoring/composition-root/" data-link-title="7.7 composition root 與依賴組裝" data-link-desc="把具體 adapter、config 與 usecase wiring 留在應用入口層">composition root 與依賴組裝</a> 對照。</p>
<h3 id="高併發即時服務先看連線與事件是否長時間存在">高併發即時服務：先看連線與事件是否長時間存在</h3>
<p>高併發即時服務的核心訊號是「server 需要同時管理大量仍然在線的工作」。聊天室、即時通知、直播狀態、代理服務與邊緣網路服務，都可能同時面對大量 connection、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>、<a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a>、<a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 與 cleanup。Go 的 goroutine、channel、context 與標準網路庫讓這些生命週期可以直接寫在程式裡。</p>
<p>這類案例可以回到 <a href="/blog/go/04-concurrency/goroutine/" data-link-title="4.1 goroutine：輕量並發工作" data-link-desc="用 goroutine 啟動並發工作，並設計清楚的退出條件">goroutine：背景工作與服務生命週期</a>、<a href="/blog/go/04-concurrency/channel/" data-link-title="4.2 channel：資料傳遞與 backpressure " data-link-desc="理解 channel 如何在 goroutine 之間傳遞資料並形成 backpressure ">channel：事件流與 backpressure </a>、<a href="/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構</a> 對照。</p>
<h3 id="效能敏感遷移先看瓶頸是否集中在清楚邊界">效能敏感遷移：先看瓶頸是否集中在清楚邊界</h3>
<p>效能敏感遷移的核心訊號是「整個產品仍可沿用原本架構，但某段服務已經成為穩定性或成本瓶頸」。例如檔案同步、資料轉換、API gateway、build pipeline 或推送服務。這時 Go 常作為局部重寫選項，讓瓶頸元件取得更好的 CPU、memory、部署與並發表現。</p>
<p>這類案例可以回到 <a href="/blog/go/00-philosophy/concurrency-language-position/" data-link-title="0.5 Go 和其他並發語言的差異" data-link-desc="比較 Go、Java、C#、Rust、Node.js、Python async、Erlang/Elixir 在並發服務中的工程定位">Go 和其他並發語言的差異</a>、<a href="/blog/go-advanced/03-runtime-profiling/" data-link-title="模組三：Runtime 與效能診斷" data-link-desc="GC、memory limit、pprof、goroutine leak 與 allocation 壓力">Runtime 與效能診斷</a> 對照。</p>
<h3 id="分散式基礎設施先看主要問題是否在協調與可靠性">分散式基礎設施：先看主要問題是否在協調與可靠性</h3>
<p>分散式基礎設施的核心訊號是「系統價值來自多節點協調」。資料庫、排程器、服務治理框架與網路控制平面，都需要清楚處理 context、retry、timeout、狀態同步與觀測訊號。Go 在這裡的價值通常是簡單語法、明確錯誤路徑、標準工具鏈與可讀的並發模型。</p>
<p>這類案例可以回到 <a href="/blog/go-advanced/04-architecture-boundaries/" data-link-title="模組四：架構邊界與事件系統" data-link-desc="用事件驅動架構拆解事件來源、處理流程、狀態邊界與即時推送">架構邊界與事件系統</a>、<a href="/blog/go-advanced/07-distributed-operations/" data-link-title="模組七：跨節點與平台整合" data-link-desc="把單一 Go 服務延伸到資料庫、queue、跨節點 WebSocket、可觀測性與部署平台">跨節點與平台整合</a> 對照。</p>
<h2 id="閱讀案例的判斷順序">閱讀案例的判斷順序</h2>
<ol>
<li>先找服務壓力：併發、部署、效能、協調或長期維護。</li>
<li>再找 Go 的切入點：goroutine、標準庫、單一 binary、型別與 package 邊界。</li>
<li>最後回到章節：把案例對應到前面已學過的 Go 概念。</li>
</ol>
<p>案例閱讀的重點是建立選型判斷，而非模仿公司規模。小型服務也可能遇到長連線、背景 worker 或部署簡化問題；大型公司案例只是把這些壓力放大到更容易觀察。</p>
]]></content:encoded></item></channel></rss>