<?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>Basics on Tarragon</title><link>https://tarrragon.github.io/blog/tags/basics/</link><description>Recent content in Basics on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 22 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/basics/index.xml" rel="self" type="application/rss+xml"/><item><title>1.1 Go 專案結構與 module</title><link>https://tarrragon.github.io/blog/go/01-basics/modules/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/modules/</guid><description>&lt;p>Go 專案的邊界通常從 &lt;code>go.mod&lt;/code> 開始。它定義目前程式碼屬於哪個 module、使用哪個 Go 版本，以及依賴哪些外部套件。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>看懂 &lt;code>go.mod&lt;/code> 的三個核心欄位&lt;/li>
&lt;li>理解 module path 與 import path 的關係&lt;/li>
&lt;li>知道為什麼 Go 指令要在 module 根目錄執行&lt;/li>
&lt;li>分辨標準庫與第三方依賴&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察gomod-定義-module">【觀察】&lt;code>go.mod&lt;/code> 定義 module&lt;/h2>
&lt;p>&lt;code>go.mod&lt;/code> 的核心用途是宣告目前 module 的身份、Go 版本與外部依賴。一個 Go 專案通常會在 module 根目錄放 &lt;code>go.mod&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">module&lt;/span> &lt;span class="nx">example&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">notify&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">service&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">go&lt;/span> &lt;span class="mf">1.25.1&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="nf">require&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">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">gorilla&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nx">websocket&lt;/span> &lt;span class="nx">v1&lt;/span>&lt;span class="mf">.5.3&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;/code>&lt;/pre>&lt;/div>&lt;p>這份檔案表達三件事：module 名稱、Go 語言版本、外部依賴。&lt;/p>
&lt;h2 id="判讀module-是-go-編譯與依賴解析的單位">【判讀】module 是 Go 編譯與依賴解析的單位&lt;/h2>
&lt;p>module 的核心規則是：Go 工具鏈以 &lt;code>go.mod&lt;/code> 所在目錄作為依賴解析與 package 掃描的根。Go 工具鏈需要知道「目前這批程式碼」的根在哪裡；&lt;code>go.mod&lt;/code> 就是這個根。&lt;/p>
&lt;p>當你在 module 根目錄執行：&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">go &lt;span class="nb">test&lt;/span> ./...&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>./...&lt;/code> 的意思是測試目前 module 底下所有 package。實務上要先找到 &lt;code>go.mod&lt;/code> 所在目錄，再從那裡執行 Go 指令。&lt;/p>
&lt;h2 id="策略先分辨三種-import">【策略】先分辨三種 import&lt;/h2>
&lt;p>閱讀 import 的核心規則是：先分辨能力來源，再決定去哪裡查。讀 Go 檔案時，先把 import 分成三類：&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;code>net/http&lt;/code>, &lt;code>context&lt;/code>, &lt;code>encoding/json&lt;/code>&lt;/td>
 &lt;td>Go 內建能力&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第三方套件&lt;/td>
 &lt;td>&lt;code>github.com/gorilla/websocket&lt;/code>&lt;/td>
 &lt;td>由 &lt;code>go.mod&lt;/code> 管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>module 內部套件&lt;/td>
 &lt;td>&lt;code>example.com/notify-service/messages&lt;/code>&lt;/td>
 &lt;td>同一個 module 的其他 package&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個分類會告訴你：問題應該去查標準庫文件、第三方套件文件，還是目前 module 的其他目錄。&lt;/p>
&lt;h2 id="執行用-module-模型閱讀-maingo">【執行】用 module 模型閱讀 &lt;code>main.go&lt;/code>&lt;/h2>
&lt;p>閱讀入口程式 import 的核心方法是：先把 import 依來源分群，再判斷程式依賴哪些能力。&lt;code>main.go&lt;/code> 的 import 可以整理成這樣：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="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="s">&amp;#34;context&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="s">&amp;#34;fmt&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="s">&amp;#34;log/slog&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="s">&amp;#34;net/http&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="s">&amp;#34;os&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="s">&amp;#34;time&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="s">&amp;#34;example.com/notify-service/messages&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="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>前面是標準庫，最後一個是專案內部 package。這表示入口程式主要依賴 Go 標準庫，只有日誌訊息常數被拆到內部 &lt;code>messages&lt;/code> package。&lt;/p></description><content:encoded><![CDATA[<p>Go 專案的邊界通常從 <code>go.mod</code> 開始。它定義目前程式碼屬於哪個 module、使用哪個 Go 版本，以及依賴哪些外部套件。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>看懂 <code>go.mod</code> 的三個核心欄位</li>
<li>理解 module path 與 import path 的關係</li>
<li>知道為什麼 Go 指令要在 module 根目錄執行</li>
<li>分辨標準庫與第三方依賴</li>
</ol>
<hr>
<h2 id="觀察gomod-定義-module">【觀察】<code>go.mod</code> 定義 module</h2>
<p><code>go.mod</code> 的核心用途是宣告目前 module 的身份、Go 版本與外部依賴。一個 Go 專案通常會在 module 根目錄放 <code>go.mod</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">module</span> <span class="nx">example</span><span class="p">.</span><span class="nx">com</span><span class="o">/</span><span class="nx">notify</span><span class="o">-</span><span class="nx">service</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">go</span> <span class="mf">1.25.1</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="nf">require</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">github</span><span class="p">.</span><span class="nx">com</span><span class="o">/</span><span class="nx">gorilla</span><span class="o">/</span><span class="nx">websocket</span> <span class="nx">v1</span><span class="mf">.5.3</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>這份檔案表達三件事：module 名稱、Go 語言版本、外部依賴。</p>
<h2 id="判讀module-是-go-編譯與依賴解析的單位">【判讀】module 是 Go 編譯與依賴解析的單位</h2>
<p>module 的核心規則是：Go 工具鏈以 <code>go.mod</code> 所在目錄作為依賴解析與 package 掃描的根。Go 工具鏈需要知道「目前這批程式碼」的根在哪裡；<code>go.mod</code> 就是這個根。</p>
<p>當你在 module 根目錄執行：</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">go <span class="nb">test</span> ./...</span></span></code></pre></div><p><code>./...</code> 的意思是測試目前 module 底下所有 package。實務上要先找到 <code>go.mod</code> 所在目錄，再從那裡執行 Go 指令。</p>
<h2 id="策略先分辨三種-import">【策略】先分辨三種 import</h2>
<p>閱讀 import 的核心規則是：先分辨能力來源，再決定去哪裡查。讀 Go 檔案時，先把 import 分成三類：</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>例子</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>標準庫</td>
          <td><code>net/http</code>, <code>context</code>, <code>encoding/json</code></td>
          <td>Go 內建能力</td>
      </tr>
      <tr>
          <td>第三方套件</td>
          <td><code>github.com/gorilla/websocket</code></td>
          <td>由 <code>go.mod</code> 管理</td>
      </tr>
      <tr>
          <td>module 內部套件</td>
          <td><code>example.com/notify-service/messages</code></td>
          <td>同一個 module 的其他 package</td>
      </tr>
  </tbody>
</table>
<p>這個分類會告訴你：問題應該去查標準庫文件、第三方套件文件，還是目前 module 的其他目錄。</p>
<h2 id="執行用-module-模型閱讀-maingo">【執行】用 module 模型閱讀 <code>main.go</code></h2>
<p>閱讀入口程式 import 的核心方法是：先把 import 依來源分群，再判斷程式依賴哪些能力。<code>main.go</code> 的 import 可以整理成這樣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s">&#34;log/slog&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s">&#34;os&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s">&#34;time&#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="s">&#34;example.com/notify-service/messages&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><p>前面是標準庫，最後一個是專案內部 package。這表示入口程式主要依賴 Go 標準庫，只有日誌訊息常數被拆到內部 <code>messages</code> package。</p>
]]></content:encoded></item><item><title>模組一：Go 基礎概念</title><link>https://tarrragon.github.io/blog/go/01-basics/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/</guid><description>&lt;p>本模組帶你建立閱讀 Go 程式需要的基本模型。重點是理解 module、變數、控制流程、package、檔案拆分、函式、入口程式與 Go tooling 如何組成日常開發流程。&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/01-basics/modules/" data-link-title="1.1 Go 專案結構與 module" data-link-desc="理解 go.mod、module path 與 Go 專案的依賴邊界">1.1&lt;/a>&lt;/td>
 &lt;td>Go 專案結構與 module&lt;/td>
 &lt;td>理解 module 與 import path&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/variables-zero-values/" data-link-title="1.2 變數、零值與短變數宣告" data-link-desc="理解 Go 如何宣告、初始化與使用零值">1.2&lt;/a>&lt;/td>
 &lt;td>變數、零值與短變數宣告&lt;/td>
 &lt;td>理解 Go 如何宣告與初始化資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/control-flow/" data-link-title="1.3 控制流程：if、for、switch" data-link-desc="掌握 Go 的條件判斷、迴圈與分支控制">1.3&lt;/a>&lt;/td>
 &lt;td>控制流程：if、for、switch&lt;/td>
 &lt;td>掌握 Go 的基本流程控制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/packages/" data-link-title="1.4 package、檔案與可見性" data-link-desc="看懂 package main、檔案切分與大小寫可見性">1.4&lt;/a>&lt;/td>
 &lt;td>package、檔案與可見性&lt;/td>
 &lt;td>看懂 &lt;code>package main&lt;/code> 與大小寫可見性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/growing-files-packages/" data-link-title="1.5 從單檔到多檔案" data-link-desc="理解 Go 程式如何從 main.go 長成多檔案與多 package">1.5&lt;/a>&lt;/td>
 &lt;td>從單檔到多檔案&lt;/td>
 &lt;td>理解 Go 程式如何從 &lt;code>main.go&lt;/code> 長成多檔案與多 package&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/functions-methods/" data-link-title="1.6 函式、方法與 receiver" data-link-desc="區分普通函式、建構函式與帶 receiver 的方法">1.6&lt;/a>&lt;/td>
 &lt;td>函式、方法與 receiver&lt;/td>
 &lt;td>區分函式、建構函式與物件方法&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/main-flow/" data-link-title="1.7 從入口程式看應用啟動流程" data-link-desc="用入口程式建立 Go 程式的啟動與資料流模型">1.7&lt;/a>&lt;/td>
 &lt;td>從入口程式看應用啟動流程&lt;/td>
 &lt;td>建立 Go 應用啟動地圖&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/01-basics/go-tooling-workflow/" data-link-title="1.8 Go tooling 與日常開發流程" data-link-desc="用 go run、go test、go fmt、go mod tidy 建立 Go 專案的基本工作節奏">1.8&lt;/a>&lt;/td>
 &lt;td>Go tooling 與日常開發流程&lt;/td>
 &lt;td>用 &lt;code>go run&lt;/code>、&lt;code>go test&lt;/code>、&lt;code>go fmt&lt;/code>、&lt;code>go mod tidy&lt;/code> 建立基本工作節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="本模組使用的範例主題">本模組使用的範例主題&lt;/h2>
&lt;ul>
&lt;li>module 宣告與依賴&lt;/li>
&lt;li>變數、零值與流程控制&lt;/li>
&lt;li>單檔、多檔案與跨 package 呼叫&lt;/li>
&lt;li>入口點與應用啟動&lt;/li>
&lt;li>建構函式與方法&lt;/li>
&lt;li>receiver 與狀態方法&lt;/li>
&lt;li>Go command、format、test、module tidy&lt;/li>
&lt;/ul>
&lt;h2 id="預備知識">預備知識&lt;/h2>
&lt;ul>
&lt;li>基本變數、函式、條件判斷&lt;/li>
&lt;li>知道命令列程式或 HTTP server 的基本概念&lt;/li>
&lt;/ul>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>預計 140-170 分鐘&lt;/p></description><content:encoded><![CDATA[<p>本模組帶你建立閱讀 Go 程式需要的基本模型。重點是理解 module、變數、控制流程、package、檔案拆分、函式、入口程式與 Go tooling 如何組成日常開發流程。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/go/01-basics/modules/" data-link-title="1.1 Go 專案結構與 module" data-link-desc="理解 go.mod、module path 與 Go 專案的依賴邊界">1.1</a></td>
          <td>Go 專案結構與 module</td>
          <td>理解 module 與 import path</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/variables-zero-values/" data-link-title="1.2 變數、零值與短變數宣告" data-link-desc="理解 Go 如何宣告、初始化與使用零值">1.2</a></td>
          <td>變數、零值與短變數宣告</td>
          <td>理解 Go 如何宣告與初始化資料</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/control-flow/" data-link-title="1.3 控制流程：if、for、switch" data-link-desc="掌握 Go 的條件判斷、迴圈與分支控制">1.3</a></td>
          <td>控制流程：if、for、switch</td>
          <td>掌握 Go 的基本流程控制</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/packages/" data-link-title="1.4 package、檔案與可見性" data-link-desc="看懂 package main、檔案切分與大小寫可見性">1.4</a></td>
          <td>package、檔案與可見性</td>
          <td>看懂 <code>package main</code> 與大小寫可見性</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/growing-files-packages/" data-link-title="1.5 從單檔到多檔案" data-link-desc="理解 Go 程式如何從 main.go 長成多檔案與多 package">1.5</a></td>
          <td>從單檔到多檔案</td>
          <td>理解 Go 程式如何從 <code>main.go</code> 長成多檔案與多 package</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/functions-methods/" data-link-title="1.6 函式、方法與 receiver" data-link-desc="區分普通函式、建構函式與帶 receiver 的方法">1.6</a></td>
          <td>函式、方法與 receiver</td>
          <td>區分函式、建構函式與物件方法</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/main-flow/" data-link-title="1.7 從入口程式看應用啟動流程" data-link-desc="用入口程式建立 Go 程式的啟動與資料流模型">1.7</a></td>
          <td>從入口程式看應用啟動流程</td>
          <td>建立 Go 應用啟動地圖</td>
      </tr>
      <tr>
          <td><a href="/blog/go/01-basics/go-tooling-workflow/" data-link-title="1.8 Go tooling 與日常開發流程" data-link-desc="用 go run、go test、go fmt、go mod tidy 建立 Go 專案的基本工作節奏">1.8</a></td>
          <td>Go tooling 與日常開發流程</td>
          <td>用 <code>go run</code>、<code>go test</code>、<code>go fmt</code>、<code>go mod tidy</code> 建立基本工作節奏</td>
      </tr>
  </tbody>
</table>
<h2 id="本模組使用的範例主題">本模組使用的範例主題</h2>
<ul>
<li>module 宣告與依賴</li>
<li>變數、零值與流程控制</li>
<li>單檔、多檔案與跨 package 呼叫</li>
<li>入口點與應用啟動</li>
<li>建構函式與方法</li>
<li>receiver 與狀態方法</li>
<li>Go command、format、test、module tidy</li>
</ul>
<h2 id="預備知識">預備知識</h2>
<ul>
<li>基本變數、函式、條件判斷</li>
<li>知道命令列程式或 HTTP server 的基本概念</li>
</ul>
<h2 id="學習時間">學習時間</h2>
<p>預計 140-170 分鐘</p>
]]></content:encoded></item><item><title>1.1 Python 哲學與設計理念</title><link>https://tarrragon.github.io/blog/python/01-basics/philosophy/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/philosophy/</guid><description>&lt;p>在學習任何程式語言之前，了解其設計理念能幫助你寫出更符合語言風格的程式碼。Python 有一套廣為人知的設計原則，被稱為「Python 之禪」。&lt;/p>
&lt;h2 id="python-之禪">Python 之禪&lt;/h2>
&lt;p>在 Python 直譯器中輸入 &lt;code>import this&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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="nn">this&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">The&lt;/span> &lt;span class="n">Zen&lt;/span> &lt;span class="n">of&lt;/span> &lt;span class="n">Python&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">by&lt;/span> &lt;span class="n">Tim&lt;/span> &lt;span class="n">Peters&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">Beautiful&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="n">ugly&lt;/span>&lt;span class="o">.&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">Explicit&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="n">implicit&lt;/span>&lt;span class="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="n">Simple&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="nb">complex&lt;/span>&lt;span class="o">.&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">Complex&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="n">complicated&lt;/span>&lt;span class="o">.&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">Flat&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="n">nested&lt;/span>&lt;span class="o">.&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">Sparse&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">better&lt;/span> &lt;span class="n">than&lt;/span> &lt;span class="n">dense&lt;/span>&lt;span class="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="n">Readability&lt;/span> &lt;span class="n">counts&lt;/span>&lt;span class="o">.&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">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="實踐中的-python-哲學">實踐中的 Python 哲學&lt;/h2>
&lt;h3 id="顯式優於隱式explicit-is-better-than-implicit">顯式優於隱式（Explicit is better than implicit）&lt;/h3>
&lt;p>在 Hook 系統中，我們明確導入需要的函式，而不是使用 &lt;code>import *&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="kn">from&lt;/span> &lt;span class="nn">lib.hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">write_hook_output&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">lib.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&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="kn">from&lt;/span> &lt;span class="nn">lib.hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="c1"># 不知道導入了什麼&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="可讀性很重要readability-counts">可讀性很重要（Readability counts）&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">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"> 3&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"> 4&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"> 5&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"> 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">def&lt;/span> &lt;span class="nf">gcb&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">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">o&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rgc&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">10&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">o&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">o&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="簡單優於複雜simple-is-better-than-complex">簡單優於複雜（Simple is better than complex）&lt;/h3>
&lt;p>Hook 系統使用簡單的 &lt;code>(bool, str)&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">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="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">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"> 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"> 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"> tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
&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="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">10&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">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="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">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">15&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">16&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">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 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">19&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;p>這個設計比拋出異常更直觀，呼叫者可以用簡單的 if 來處理：&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="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>&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">success&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&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">else&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">output&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="扁平優於巢狀flat-is-better-than-nested">扁平優於巢狀（Flat is better than nested）&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">check_file&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">if&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">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"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">suffix&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;.py&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">if&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stat&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">st_size&lt;/span> &lt;span class="o">&amp;gt;&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"> 7&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"> 8&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"> 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"># 好：使用 early return&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">check_file&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&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">13&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">14&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">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">15&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">16&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">suffix&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s1">&amp;#39;.py&amp;#39;&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>&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">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stat&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">st_size&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">19&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">20&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;/code>&lt;/pre>&lt;/div>&lt;h2 id="pythonic的含義">「Pythonic」的含義&lt;/h2>
&lt;p>當我們說程式碼是「Pythonic」時，意思是它遵循 Python 的慣例和風格。以下是一些例子：&lt;/p>
&lt;h3 id="使用-list-comprehension">使用 List Comprehension&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"># Pythonic&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">squares&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">x&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">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"># 非 Pythonic&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">squares&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">x&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">7&lt;/span>&lt;span class="cl"> &lt;span class="n">squares&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">x&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">2&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"># Pythonic&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">with&lt;/span> &lt;span class="nb">open&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="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">3&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">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"># 非 Pythonic&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">f&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">open&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="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>&lt;/span>&lt;span class="line">&lt;span class="ln">7&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">8&lt;/span>&lt;span class="cl">&lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">close&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;h3 id="使用-enumerate-而非-rangelen">使用 enumerate 而非 range(len())&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"># Pythonic&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">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&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">items&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="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">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">item&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">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"># 非 Pythonic&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">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="nb">len&lt;/span>&lt;span class="p">(&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">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;&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="si">{&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&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="實際範例hook-系統的設計體現">實際範例：Hook 系統的設計體現&lt;/h2>
&lt;p>來自 &lt;code>.claude/lib/hook_io.py&lt;/code> 的設計展示了這些原則：&lt;/p></description><content:encoded><![CDATA[<p>在學習任何程式語言之前，了解其設計理念能幫助你寫出更符合語言風格的程式碼。Python 有一套廣為人知的設計原則，被稱為「Python 之禪」。</p>
<h2 id="python-之禪">Python 之禪</h2>
<p>在 Python 直譯器中輸入 <code>import this</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="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">this</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">The</span> <span class="n">Zen</span> <span class="n">of</span> <span class="n">Python</span><span class="p">,</span> <span class="n">by</span> <span class="n">Tim</span> <span class="n">Peters</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">Beautiful</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="n">ugly</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">Explicit</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="n">implicit</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">Simple</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="nb">complex</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">Complex</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="n">complicated</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">Flat</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="n">nested</span><span class="o">.</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">Sparse</span> <span class="ow">is</span> <span class="n">better</span> <span class="n">than</span> <span class="n">dense</span><span class="o">.</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">Readability</span> <span class="n">counts</span><span class="o">.</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="o">...</span></span></span></code></pre></div><h2 id="實踐中的-python-哲學">實踐中的 Python 哲學</h2>
<h3 id="顯式優於隱式explicit-is-better-than-implicit">顯式優於隱式（Explicit is better than implicit）</h3>
<p>在 Hook 系統中，我們明確導入需要的函式，而不是使用 <code>import *</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="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span>
</span></span><span class="line"><span class="ln">3</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></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="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="o">*</span>  <span class="c1"># 不知道導入了什麼</span></span></span></code></pre></div><h3 id="可讀性很重要readability-counts">可讀性很重要（Readability counts）</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">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"> 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">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"> 5</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"> 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">def</span> <span class="nf">gcb</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">s</span><span class="p">,</span> <span class="n">o</span> <span class="o">=</span> <span class="n">rgc</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">10</span><span class="cl">    <span class="k">return</span> <span class="n">o</span> <span class="k">if</span> <span class="n">s</span> <span class="ow">and</span> <span class="n">o</span> <span class="k">else</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="簡單優於複雜simple-is-better-than-complex">簡單優於複雜（Simple is better than complex）</h3>
<p>Hook 系統使用簡單的 <code>(bool, str)</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">run_git_command</span><span class="p">(</span><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 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"> 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">    Returns:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        tuple[bool, str]: (是否成功, 輸出內容或錯誤訊息)
</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="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">10</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">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="p">)</span>
</span></span><span class="line"><span class="ln">14</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">15</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">16</span><span class="cl">        <span class="k">else</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 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">19</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><p>這個設計比拋出異常更直觀，呼叫者可以用簡單的 if 來處理：</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">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></span><span class="line"><span class="ln">2</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">3</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">else</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="sa">f</span><span class="s2">&#34;Error: </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></code></pre></div><h3 id="扁平優於巢狀flat-is-better-than-nested">扁平優於巢狀（Flat is better than nested）</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">check_file</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="k">if</span> <span class="n">path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">if</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"> 5</span><span class="cl">            <span class="k">if</span> <span class="n">path</span><span class="o">.</span><span class="n">suffix</span> <span class="o">==</span> <span class="s1">&#39;.py&#39;</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">path</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span> <span class="o">&gt;</span> <span class="mi">0</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="kc">True</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</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="c1"># 好：使用 early return</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="n">path</span><span class="p">):</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">path</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="kc">False</span>
</span></span><span class="line"><span class="ln">14</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">15</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">if</span> <span class="n">path</span><span class="o">.</span><span class="n">suffix</span> <span class="o">!=</span> <span class="s1">&#39;.py&#39;</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></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="n">path</span><span class="o">.</span><span class="n">stat</span><span class="p">()</span><span class="o">.</span><span class="n">st_size</span> <span class="o">&lt;=</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="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="kc">True</span></span></span></code></pre></div><h2 id="pythonic的含義">「Pythonic」的含義</h2>
<p>當我們說程式碼是「Pythonic」時，意思是它遵循 Python 的慣例和風格。以下是一些例子：</p>
<h3 id="使用-list-comprehension">使用 List Comprehension</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"># Pythonic</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">squares</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">x</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">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 非 Pythonic</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">squares</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">x</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">7</span><span class="cl">    <span class="n">squares</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">x</span><span class="o">**</span><span class="mi">2</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"># Pythonic</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_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">3</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">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 非 Pythonic</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_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></span><span class="line"><span class="ln">7</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">8</span><span class="cl"><span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>  <span class="c1"># 容易忘記關閉</span></span></span></code></pre></div><h3 id="使用-enumerate-而非-rangelen">使用 enumerate 而非 range(len())</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"># Pythonic</span>
</span></span><span class="line"><span class="ln">2</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">items</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</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">i</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">item</span><span class="si">}</span><span class="s2">&#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="c1"># 非 Pythonic</span>
</span></span><span class="line"><span class="ln">6</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="nb">len</span><span class="p">(</span><span class="n">items</span><span class="p">)):</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;</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">items</span><span class="p">[</span><span class="n">i</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="實際範例hook-系統的設計體現">實際範例：Hook 系統的設計體現</h2>
<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">write_hook_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">output</span><span class="p">:</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="n">ensure_ascii</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"> 4</span><span class="cl">    <span class="n">indent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">2</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="kc">None</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">    輸出 Hook 結果到 stdout
</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">        output: 要輸出的字典
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        ensure_ascii: 是否確保 ASCII 編碼（預設 False 以支援中文）
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        indent: JSON 縮排空格數
</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="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">output</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="n">ensure_ascii</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">))</span></span></span></code></pre></div><p>這個函式體現了：</p>
<ul>
<li><strong>顯式參數</strong>：每個參數都有預設值和明確的型別提示</li>
<li><strong>文件字串</strong>：清楚說明函式用途和參數意義</li>
<li><strong>單一職責</strong>：函式只做一件事 - 輸出 JSON</li>
</ul>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>ensure_ascii=False</code> 是預設值？</li>
<li>Hook 系統為什麼選擇返回 <code>tuple[bool, str]</code> 而不是拋出異常？</li>
<li>閱讀 <code>.claude/lib/git_utils.py</code>，找出三個體現 Python 哲學的設計選擇。</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0020/">PEP 20 - The Zen of Python</a></li>
<li><a href="https://peps.python.org/pep-0008/">PEP 8 - Style Guide for Python Code</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">模組與套件組織</a></p>
]]></content:encoded></item><item><title>6.1 如何新增一個 Hook</title><link>https://tarrragon.github.io/blog/python/06-practical/new-hook/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/06-practical/new-hook/</guid><description>&lt;p>本章介紹如何從零開始建立一個 Claude Code Hook 腳本。這是一個實戰指南，整合了前面學到的所有概念。&lt;/p>
&lt;h2 id="前置知識">前置知識&lt;/h2>
&lt;p>建議先閱讀：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/json/" data-link-title="3.2 json - 序列化" data-link-desc="資料的讀寫與轉換">3.2 json 序列化&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/logging/" data-link-title="3.5 logging - 日誌系統" data-link-desc="結構化日誌輸出與除錯">3.5 logging 日誌系統&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/04-oop/class-design/" data-link-title="4.1 類別設計原則" data-link-desc="設計清晰的類別介面">4.1 類別設計原則&lt;/a>&lt;/li>
&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;/ul>
&lt;h2 id="hook-系統概述">Hook 系統概述&lt;/h2>
&lt;p>Claude Code Hook 是在特定事件發生時執行的腳本，例如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>SessionStart&lt;/code> - 會話開始&lt;/li>
&lt;li>&lt;code>Stop&lt;/code> - Claude 主動結束&lt;/li>
&lt;li>&lt;code>PreToolUse&lt;/code> - 工具使用前&lt;/li>
&lt;li>&lt;code>PostToolUse&lt;/code> - 工具使用後&lt;/li>
&lt;/ul>
&lt;h2 id="步驟-1建立基本結構">步驟 1：建立基本結構&lt;/h2>
&lt;h3 id="使用-uv-單檔模式">使用 UV 單檔模式&lt;/h3>
&lt;p>推薦使用 &lt;code>uv&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="ch">#!/usr/bin/env -S uv run --quiet --script&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"># /// script&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"># requires-python = &amp;#34;&amp;gt;=3.10&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="c1"># dependencies = []&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="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">My Custom Hook - 簡短描述
&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">Hook Event: SessionStart
&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">&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="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&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">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"># 添加 lib 目錄到路徑&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">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&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>&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">hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&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">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">23&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">24&lt;/span>&lt;span class="cl"> &lt;span class="n">logger&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">setup_hook_logging&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;my_custom_hook&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">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hook 開始執行&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"># 你的邏輯在這裡&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">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hook 執行完成&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="k">return&lt;/span> &lt;span class="mi">0&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="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">33&lt;/span>&lt;span class="cl"> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exit&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;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="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">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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 添加 lib 目錄到路徑&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">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&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="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="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&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">hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&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">hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">write_hook_output&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-2處理輸入輸出">步驟 2：處理輸入輸出&lt;/h2>
&lt;h3 id="sessionstart-hook-範例">SessionStart Hook 範例&lt;/h3>
&lt;p>SessionStart 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="ch">#!/usr/bin/env -S uv run --quiet --script&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"># /// script&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"># requires-python = &amp;#34;&amp;gt;=3.10&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="c1"># dependencies = []&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="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">Branch Status Reminder - 分支狀態提醒
&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">Hook Event: SessionStart
&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="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&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">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">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">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&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>&lt;/span>&lt;span class="line">&lt;span class="ln">17&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">18&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">19&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">20&lt;/span>&lt;span class="cl"> &lt;span class="n">is_protected_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 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>&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">main&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="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">26&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;Branch Status Reminder&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="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">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">current_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">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">current_branch&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="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">32&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&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">is_protected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_branch&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">branch_status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;保護分支&amp;#34;&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&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>&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;當前分支: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">current_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">branch_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">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;工作目錄: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">get_project_root&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>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected&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="nb">print&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="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">43&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;建議: 建立 feature 分支後再進行開發&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>&lt;/span>&lt;span class="line">&lt;span class="ln">45&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">46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&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>&lt;/span>&lt;span class="line">&lt;span class="ln">49&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">50&lt;/span>&lt;span class="cl"> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exit&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;h3 id="pretooluseposttooluse-hook-範例">PreToolUse/PostToolUse Hook 範例&lt;/h3>
&lt;p>這類 Hook 需要讀取 stdin 並輸出 JSON：&lt;/p></description><content:encoded><![CDATA[<p>本章介紹如何從零開始建立一個 Claude Code Hook 腳本。這是一個實戰指南，整合了前面學到的所有概念。</p>
<h2 id="前置知識">前置知識</h2>
<p>建議先閱讀：</p>
<ul>
<li><a href="/blog/python/03-stdlib/json/" data-link-title="3.2 json - 序列化" data-link-desc="資料的讀寫與轉換">3.2 json 序列化</a></li>
<li><a href="/blog/python/03-stdlib/logging/" data-link-title="3.5 logging - 日誌系統" data-link-desc="結構化日誌輸出與除錯">3.5 logging 日誌系統</a></li>
<li><a href="/blog/python/04-oop/class-design/" data-link-title="4.1 類別設計原則" data-link-desc="設計清晰的類別介面">4.1 類別設計原則</a></li>
<li><a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></li>
</ul>
<h2 id="hook-系統概述">Hook 系統概述</h2>
<p>Claude Code Hook 是在特定事件發生時執行的腳本，例如：</p>
<ul>
<li><code>SessionStart</code> - 會話開始</li>
<li><code>Stop</code> - Claude 主動結束</li>
<li><code>PreToolUse</code> - 工具使用前</li>
<li><code>PostToolUse</code> - 工具使用後</li>
</ul>
<h2 id="步驟-1建立基本結構">步驟 1：建立基本結構</h2>
<h3 id="使用-uv-單檔模式">使用 UV 單檔模式</h3>
<p>推薦使用 <code>uv</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 -S uv run --quiet --script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># /// script</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># requires-python = &#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># dependencies = []</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">My Custom Hook - 簡短描述
</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">Hook Event: SessionStart
</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="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">15</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">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 添加 lib 目錄到路徑</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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="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">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">main</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">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">25</span><span class="cl">    <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&#34;Hook 開始執行&#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"># 你的邏輯在這裡</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">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&#34;Hook 執行完成&#34;</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="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="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">33</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><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">sys</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></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 添加 lib 目錄到路徑</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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"># 現在可以導入共用模組</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln"> 9</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">10</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span></span></span></code></pre></div><h2 id="步驟-2處理輸入輸出">步驟 2：處理輸入輸出</h2>
<h3 id="sessionstart-hook-範例">SessionStart Hook 範例</h3>
<p>SessionStart 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="ch">#!/usr/bin/env -S uv run --quiet --script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># /// script</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># requires-python = &#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># dependencies = []</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">Branch Status Reminder - 分支狀態提醒
</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">Hook Event: SessionStart
</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">sys</span>
</span></span><span class="line"><span class="ln">13</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">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</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="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">18</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">is_protected_branch</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></span><span class="line"><span class="ln">24</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">25</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">26</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Branch Status Reminder&#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="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">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">current_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">30</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">current_branch</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="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</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="n">is_protected</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">branch_status</span> <span class="o">=</span> <span class="s2">&#34;保護分支&#34;</span> <span class="k">if</span> <span class="n">is_protected</span> <span class="k">else</span> <span class="s2">&#34;開發分支&#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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;當前分支: </span><span class="si">{</span><span class="n">current_branch</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">branch_status</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;工作目錄: </span><span class="si">{</span><span class="n">get_project_root</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></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">if</span> <span class="n">is_protected</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></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="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="s2">&#34;建議: 建立 feature 分支後再進行開發&#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="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="k">return</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></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="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h3 id="pretooluseposttooluse-hook-範例">PreToolUse/PostToolUse Hook 範例</h3>
<p>這類 Hook 需要讀取 stdin 並輸出 JSON：</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 -S uv run --quiet --script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># /// script</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># requires-python = &#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># dependencies = []</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">Tool Usage Logger - 記錄工具使用情況
</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">Hook Event: PostToolUse
</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">sys</span>
</span></span><span class="line"><span class="ln">13</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">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</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="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span>
</span></span><span class="line"><span class="ln">18</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">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 class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">22</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;tool_usage_logger&#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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># 讀取 Hook 輸入</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">hook_input</span> <span class="o">=</span> <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></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="n">hook_input</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">write_hook_output</span><span class="p">(</span><span class="n">continue_execution</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 class="k">return</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="c1"># 取得工具資訊</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">tool_name</span> <span class="o">=</span> <span class="n">hook_input</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;unknown&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">tool_input</span> <span class="o">=</span> <span class="n">hook_input</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">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="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;工具使用: </span><span class="si">{</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">38</span><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;輸入參數: </span><span class="si">{</span><span class="n">tool_input</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="c1"># 輸出結果（不阻擋執行）</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">write_hook_output</span><span class="p">(</span><span class="n">continue_execution</span><span class="o">=</span><span class="kc">True</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">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">44</span><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</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 class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="n">write_hook_output</span><span class="p">(</span><span class="n">continue_execution</span><span class="o">=</span><span class="kc">True</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="mi">0</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></span><span class="line"><span class="ln">50</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">51</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h2 id="步驟-3使用共用模組">步驟 3：使用共用模組</h2>
<h3 id="git-工具">Git 工具</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">git_utils</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">run_git_command</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">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">is_allowed_branch</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="c1"># 取得當前分支</span>
</span></span><span class="line"><span class="ln">10</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">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></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="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"># 執行 git 命令</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="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;--short&#34;</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">success</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="n">output</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">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"> 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">logger</span> <span class="o">=</span> <span class="n">setup_hook_logging</span><span class="p">(</span><span class="s2">&#34;my_hook&#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">logger</span><span class="o">.</span><span class="n">debug</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="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</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="n">logger</span><span class="o">.</span><span class="n">warning</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 class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s2">&#34;錯誤訊息&#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="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"> 2</span><span class="cl">    <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="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">create_posttooluse_output</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"># 讀取輸入</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">hook_input</span> <span class="o">=</span> <span class="n">read_hook_input</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">write_hook_output</span><span class="p">(</span><span class="n">continue_execution</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></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">write_hook_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">continue_execution</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="n">decision</span><span class="o">=</span><span class="s2">&#34;block&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">reason</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 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"># PreToolUse 專用輸出</span>
</span></span><span class="line"><span class="ln">22</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">23</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 class="c1"># 或 &#34;block&#34;, &#34;modify&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">reason</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 class="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">output</span><span class="p">))</span></span></span></code></pre></div><h2 id="步驟-4註冊-hook">步驟 4：註冊 Hook</h2>
<p>在 <code>.claude/settings.json</code> 中註冊：</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;hooks&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nt">&#34;SessionStart&#34;</span><span class="p">:</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 class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python .claude/hooks/branch-status-reminder.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nt">&#34;event&#34;</span><span class="p">:</span> <span class="s2">&#34;SessionStart&#34;</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 class="p">],</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nt">&#34;PreToolUse&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python .claude/hooks/tool-guard.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="nt">&#34;event&#34;</span><span class="p">:</span> <span class="s2">&#34;PreToolUse&#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="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="p">}</span></span></span></code></pre></div><h2 id="步驟-5撰寫測試">步驟 5：撰寫測試</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="c1"># tests/test_my_hook.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">My Hook 測試
</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">unittest</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</span><span class="p">,</span> <span class="n">MagicMock</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</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></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 添加 hooks 目錄到路徑</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</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">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 class="k">class</span> <span class="nc">TestMyHook</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 My Hook&#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="nd">@patch</span><span class="p">(</span><span class="s2">&#34;my_hook.get_current_branch&#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">test_protected_branch_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_branch</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="n">mock_branch</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="s2">&#34;main&#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="c1"># 導入並測試</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="kn">from</span> <span class="nn">my_hook</span> <span class="kn">import</span> <span class="n">check_branch_status</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="n">result</span> <span class="o">=</span> <span class="n">check_branch_status</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">assertTrue</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;is_protected&#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="nd">@patch</span><span class="p">(</span><span class="s2">&#34;my_hook.read_hook_input&#34;</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">test_empty_input_handling</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_input</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="n">mock_input</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="kc">None</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="kn">from</span> <span class="nn">my_hook</span> <span class="kn">import</span> <span class="n">main</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="n">result</span> <span class="o">=</span> <span class="n">main</span><span class="p">()</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">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">0</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></span><span class="line"><span class="ln">41</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">42</span><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h2 id="完整範例檔案類型檢查-hook">完整範例：檔案類型檢查 Hook</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 -S uv run --quiet --script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># /// script</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># requires-python = &#34;&gt;=3.10&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># dependencies = []</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">File Type Permission Hook - 檔案類型權限檢查
</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">Hook Event: PreToolUse
</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="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">15</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">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">create_pretooluse_output</span>
</span></span><span class="line"><span class="ln">20</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">21</span><span class="cl"><span class="kn">import</span> <span class="nn">json</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></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">PROTECTED_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;*.env&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;*.pem&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;*.key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="s2">&#34;*credentials*&#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">MONITORED_TOOLS</span> <span class="o">=</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 class="s2">&#34;Bash&#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></span><span class="line"><span class="ln">36</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">37</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;file_type_permission&#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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">hook_input</span> <span class="o">=</span> <span class="n">read_hook_input</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="n">hook_input</span> <span class="ow">is</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="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">create_pretooluse_output</span><span class="p">(</span><span class="s2">&#34;allow&#34;</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="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="n">tool_name</span> <span class="o">=</span> <span class="n">hook_input</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">47</span><span class="cl">        <span class="n">tool_input</span> <span class="o">=</span> <span class="n">hook_input</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">48</span><span class="cl">
</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">tool_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">MONITORED_TOOLS</span><span class="p">:</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="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">create_pretooluse_output</span><span class="p">(</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="k">return</span> <span class="mi">0</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="n">file_path</span> <span class="o">=</span> <span class="n">tool_input</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;file_path&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="n">tool_input</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;command&#34;</span><span class="p">,</span> <span class="s2">&#34;&#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="c1"># 檢查是否為受保護的檔案</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="k">if</span> <span class="n">is_protected_file</span><span class="p">(</span><span class="n">file_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">59</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;阻擋對受保護檔案的操作: </span><span class="si">{</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">60</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">61</span><span class="cl">                <span class="n">decision</span><span class="o">=</span><span class="s2">&#34;block&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="n">reason</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">63</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</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 class="s2">&#34;allow&#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="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">output</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">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">70</span><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</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 class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">create_pretooluse_output</span><span class="p">(</span><span class="s2">&#34;allow&#34;</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="mi">0</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></span><span class="line"><span class="ln">76</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_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="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查檔案是否受保護&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</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="kc">False</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">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">82</span><span class="cl">
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_PATTERNS</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">path</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">pattern</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">True</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="kc">False</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></span><span class="line"><span class="ln">90</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">91</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span></span></span></code></pre></div><h2 id="開發檢查清單">開發檢查清單</h2>
<p>新增 Hook 時的檢查項目：</p>
<ul>
<li><input disabled="" type="checkbox"> 使用 UV 單檔模式（<code>#!/usr/bin/env -S uv run --quiet --script</code>）</li>
<li><input disabled="" type="checkbox"> 正確設定 <code>sys.path</code> 以導入共用模組</li>
<li><input disabled="" type="checkbox"> 使用 <code>setup_hook_logging()</code> 設定日誌</li>
<li><input disabled="" type="checkbox"> 使用 <code>read_hook_input()</code> / <code>write_hook_output()</code> 處理 I/O</li>
<li><input disabled="" type="checkbox"> 妥善處理異常（不讓 Hook 崩潰影響系統）</li>
<li><input disabled="" type="checkbox"> 在 <code>settings.json</code> 中註冊</li>
<li><input disabled="" type="checkbox"> 撰寫單元測試</li>
<li><input disabled="" type="checkbox"> 更新文件</li>
</ul>
<h2 id="常見問題">常見問題</h2>
<h3 id="q-hook-執行失敗會影響-claude-嗎">Q: Hook 執行失敗會影響 Claude 嗎？</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">main</span><span class="p">():</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="c1"># 你的邏輯</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 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"> 6</span><span class="cl">        <span class="n">logger</span><span class="o">.</span><span class="n">error</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"> 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">write_hook_output</span><span class="p">(</span><span class="n">continue_execution</span><span class="o">=</span><span class="kc">True</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="mi">0</span>  <span class="c1"># 總是返回 0</span></span></span></code></pre></div><h3 id="q-如何調試-hook">Q: 如何調試 Hook？</h3>
<ol>
<li>使用日誌：</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">logger</span> <span class="o">=</span> <span class="n">setup_hook_logging</span><span class="p">(</span><span class="s2">&#34;my_hook&#34;</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">logger</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;輸入資料: </span><span class="si">{</span><span class="n">hook_input</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><ol start="2">
<li>查看日誌檔案：</li>
</ol>





<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">tail -f .claude/hook-logs/my_hook.log</span></span></code></pre></div><h3 id="q-hook-執行順序是什麼">Q: Hook 執行順序是什麼？</h3>
<p>同一事件的多個 Hook 按照 <code>settings.json</code> 中定義的順序執行。</p>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼要將 Hook 邏輯封裝在 <code>main()</code> 函式中？</li>
<li>什麼時候應該使用 <code>block</code>，什麼時候使用 <code>allow</code>？</li>
<li>如何設計 Hook 以便於測試？</li>
</ol>
<hr>
<p><em>下一章：<a href="/blog/python/06-practical/extend-lib/" data-link-title="6.2 如何擴展共用模組" data-link-desc="為 Hook 系統添加新功能">如何擴展共用模組</a></em>
<em>回到首頁：<a href="/blog/python/" data-link-title="Python 維護工程師實戰指南" data-link-desc="以 Hook 系統為範例的 Python 開發教學">Python 維護工程師實戰指南</a></em></p>
]]></content:encoded></item><item><title>模組一：Python 基礎概念</title><link>https://tarrragon.github.io/blog/python/01-basics/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/</guid><description>&lt;p>本模組帶你快速回顧 Python 的核心概念。如果你已經有其他程式語言經驗，這些內容能幫助你理解 Python 的「思考方式」，以及程式如何從單一 script 長成多檔案與 package。&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/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">1.1&lt;/a>&lt;/td>
 &lt;td>Python 哲學與設計理念&lt;/td>
 &lt;td>理解「Pythonic」的真正含義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">1.2&lt;/a>&lt;/td>
 &lt;td>從單一 script 到多檔案專案&lt;/td>
 &lt;td>理解 &lt;code>.py&lt;/code> module、package、執行方式與測試結構如何逐步出現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.3&lt;/a>&lt;/td>
 &lt;td>模組與套件組織&lt;/td>
 &lt;td>掌握 &lt;code>__init__.py&lt;/code> 的作用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">1.4&lt;/a>&lt;/td>
 &lt;td>導入機制與路徑管理&lt;/td>
 &lt;td>解決「找不到模組」的困擾&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="實際範例來源">實際範例來源&lt;/h2>
&lt;p>本模組的範例主要來自：&lt;/p>
&lt;ul>
&lt;li>&lt;code>.claude/lib/__init__.py&lt;/code> - 模組初始化範例&lt;/li>
&lt;li>Hook 腳本的導入結構&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>後續補寫提示：&lt;code>1.2 從單一 script 到多檔案專案&lt;/code> 應改用中立範例，避免依賴特定 Hook 系統背景。這一章負責補上初學者從單檔到 package 的過渡；&lt;code>1.3&lt;/code> 和 &lt;code>1.4&lt;/code> 再處理既有專案中的 module、package 與 import 問題。&lt;/p>&lt;/blockquote>
&lt;h2 id="預備知識">預備知識&lt;/h2>
&lt;ul>
&lt;li>基本程式設計概念（變數、函式、迴圈）&lt;/li>
&lt;li>對任一程式語言有基礎了解&lt;/li>
&lt;/ul>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>預計 45-60 分鐘&lt;/p></description><content:encoded><![CDATA[<p>本模組帶你快速回顧 Python 的核心概念。如果你已經有其他程式語言經驗，這些內容能幫助你理解 Python 的「思考方式」，以及程式如何從單一 script 長成多檔案與 package。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">1.1</a></td>
          <td>Python 哲學與設計理念</td>
          <td>理解「Pythonic」的真正含義</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">1.2</a></td>
          <td>從單一 script 到多檔案專案</td>
          <td>理解 <code>.py</code> module、package、執行方式與測試結構如何逐步出現</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.3</a></td>
          <td>模組與套件組織</td>
          <td>掌握 <code>__init__.py</code> 的作用</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">1.4</a></td>
          <td>導入機制與路徑管理</td>
          <td>解決「找不到模組」的困擾</td>
      </tr>
  </tbody>
</table>
<h2 id="實際範例來源">實際範例來源</h2>
<p>本模組的範例主要來自：</p>
<ul>
<li><code>.claude/lib/__init__.py</code> - 模組初始化範例</li>
<li>Hook 腳本的導入結構</li>
</ul>
<blockquote>
<p>後續補寫提示：<code>1.2 從單一 script 到多檔案專案</code> 應改用中立範例，避免依賴特定 Hook 系統背景。這一章負責補上初學者從單檔到 package 的過渡；<code>1.3</code> 和 <code>1.4</code> 再處理既有專案中的 module、package 與 import 問題。</p></blockquote>
<h2 id="預備知識">預備知識</h2>
<ul>
<li>基本程式設計概念（變數、函式、迴圈）</li>
<li>對任一程式語言有基礎了解</li>
</ul>
<h2 id="學習時間">學習時間</h2>
<p>預計 45-60 分鐘</p>
]]></content:encoded></item><item><title>1.2 從單一 script 到多檔案專案</title><link>https://tarrragon.github.io/blog/python/01-basics/script-to-package/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/script-to-package/</guid><description>&lt;p>Python 程式變大的第一個斷點通常是執行方式與 import 邊界，而非物件導向或架構分層。初學者常從一個 &lt;code>script.py&lt;/code> 開始，接著拆出 helper module，最後才整理成 package；每一步都會改變程式如何被執行、如何 import，以及測試如何找到程式碼。&lt;/p>
&lt;blockquote>
&lt;p>撰寫提示：本章先保留大綱，詳細內容之後補。補寫時請使用中立範例，例如 &lt;code>notify.py&lt;/code>、&lt;code>config.py&lt;/code>、&lt;code>parser.py&lt;/code>、&lt;code>service.py&lt;/code>，避免綁定特定專案或 Hook 系統細節。&lt;/p>&lt;/blockquote>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>判斷何時保留單一 script&lt;/li>
&lt;li>理解一個 &lt;code>.py&lt;/code> 檔案就是一個 module&lt;/li>
&lt;li>分辨「同層多檔案」與「package」的差異&lt;/li>
&lt;li>看懂 &lt;code>python file.py&lt;/code> 與 &lt;code>python -m package.module&lt;/code> 的差異&lt;/li>
&lt;li>判斷何時需要 &lt;code>__init__.py&lt;/code>、&lt;code>pyproject.toml&lt;/code> 或 &lt;code>src/&lt;/code> layout&lt;/li>
&lt;/ol>
&lt;h2 id="章節大綱">章節大綱&lt;/h2>
&lt;h3 id="1-單一-script-是合理起點">1. 單一 script 是合理起點&lt;/h3>
&lt;p>核心原則：小工具與實驗程式可以先從單一 &lt;code>.py&lt;/code> 檔案開始。這個階段的重點是讓流程清楚，不是急著拆資料夾。&lt;/p>
&lt;p>後續補寫範例：&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">notify.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>if __name__ == &amp;quot;__main__&amp;quot;&lt;/code> 的基本用途。&lt;/li>
&lt;li>函式先集中在同一檔案，避免過早拆分。&lt;/li>
&lt;li>當檔案開始同時包含 CLI、設定、解析、業務規則時，再考慮拆檔。&lt;/li>
&lt;/ul>
&lt;h3 id="2-拆成同層-module">2. 拆成同層 module&lt;/h3>
&lt;p>核心原則：Python 的每個 &lt;code>.py&lt;/code> 檔案都是 module。同層拆檔可以降低單檔負擔，但 import 會受到目前執行位置與 &lt;code>sys.path&lt;/code> 影響。&lt;/p>
&lt;p>後續補寫範例：&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── config.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── parser.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── service.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>import config&lt;/code> 與 &lt;code>from config import load_config&lt;/code>。&lt;/li>
&lt;li>從專案根目錄執行與從其他目錄執行的差異。&lt;/li>
&lt;li>同層 module 適合小型工具，但不一定適合長期擴張。&lt;/li>
&lt;/ul>
&lt;h3 id="3-整理成-package">3. 整理成 package&lt;/h3>
&lt;p>核心原則：package 是一組 module 的命名空間。當多個 module 共同形成一個概念，就可以用資料夾與 &lt;code>__init__.py&lt;/code> 整理成 package。&lt;/p>
&lt;p>後續補寫範例：&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify_app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">│ ├── config.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">│ ├── parser.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">│ └── service.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">└── main.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>__init__.py&lt;/code> 的角色：初始化、公開 API、package 標記。&lt;/li>
&lt;li>package 內部使用 absolute import 或 relative import 的取捨。&lt;/li>
&lt;li>不要把所有名稱都重新 export 到 &lt;code>__init__.py&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="4-執行方式會影響-import">4. 執行方式會影響 import&lt;/h3>
&lt;p>核心原則：Python 的 import 行為和執行方式密切相關。&lt;code>python main.py&lt;/code>、&lt;code>python -m package.module&lt;/code>、測試工具與安裝後執行，可能會看到不同的 module search path。&lt;/p>
&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">python main.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">python -m notify_app.cli
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">python -m pytest&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>__name__&lt;/code> 與 &lt;code>__package__&lt;/code> 的差異。&lt;/li>
&lt;li>為什麼 package 內相對 import 在直接執行檔案時容易失敗。&lt;/li>
&lt;li>CLI 入口和 library module 最好分開。&lt;/li>
&lt;/ul>
&lt;h3 id="5-測試會推動專案結構">5. 測試會推動專案結構&lt;/h3>
&lt;p>核心原則：當程式需要測試時，專案結構必須讓測試穩定 import 目標程式碼。測試是拆分 module 與 package 的重要壓力來源。&lt;/p>
&lt;p>後續補寫範例：&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify_app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">│ └── service.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"> └── test_service.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>tests/&lt;/code> 和 package 的相對位置。&lt;/li>
&lt;li>為什麼測試常揭露 import 路徑設計不穩。&lt;/li>
&lt;li>小型專案可以先維持簡單，大型或可發布專案再導入 &lt;code>pyproject.toml&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="6-何時進入可安裝專案與-src-layout">6. 何時進入可安裝專案與 &lt;code>src/&lt;/code> layout&lt;/h3>
&lt;p>核心原則：&lt;code>pyproject.toml&lt;/code> 與 &lt;code>src/&lt;/code> layout 是正式專案管理工具，不是所有初學程式的起點。當專案需要被安裝、發布、由多個工具穩定執行時，再導入這些結構。&lt;/p></description><content:encoded><![CDATA[<p>Python 程式變大的第一個斷點通常是執行方式與 import 邊界，而非物件導向或架構分層。初學者常從一個 <code>script.py</code> 開始，接著拆出 helper module，最後才整理成 package；每一步都會改變程式如何被執行、如何 import，以及測試如何找到程式碼。</p>
<blockquote>
<p>撰寫提示：本章先保留大綱，詳細內容之後補。補寫時請使用中立範例，例如 <code>notify.py</code>、<code>config.py</code>、<code>parser.py</code>、<code>service.py</code>，避免綁定特定專案或 Hook 系統細節。</p></blockquote>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>判斷何時保留單一 script</li>
<li>理解一個 <code>.py</code> 檔案就是一個 module</li>
<li>分辨「同層多檔案」與「package」的差異</li>
<li>看懂 <code>python file.py</code> 與 <code>python -m package.module</code> 的差異</li>
<li>判斷何時需要 <code>__init__.py</code>、<code>pyproject.toml</code> 或 <code>src/</code> layout</li>
</ol>
<h2 id="章節大綱">章節大綱</h2>
<h3 id="1-單一-script-是合理起點">1. 單一 script 是合理起點</h3>
<p>核心原則：小工具與實驗程式可以先從單一 <code>.py</code> 檔案開始。這個階段的重點是讓流程清楚，不是急著拆資料夾。</p>
<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">notify.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>if __name__ == &quot;__main__&quot;</code> 的基本用途。</li>
<li>函式先集中在同一檔案，避免過早拆分。</li>
<li>當檔案開始同時包含 CLI、設定、解析、業務規則時，再考慮拆檔。</li>
</ul>
<h3 id="2-拆成同層-module">2. 拆成同層 module</h3>
<p>核心原則：Python 的每個 <code>.py</code> 檔案都是 module。同層拆檔可以降低單檔負擔，但 import 會受到目前執行位置與 <code>sys.path</code> 影響。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify.py
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── config.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── parser.py
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>import config</code> 與 <code>from config import load_config</code>。</li>
<li>從專案根目錄執行與從其他目錄執行的差異。</li>
<li>同層 module 適合小型工具，但不一定適合長期擴張。</li>
</ul>
<h3 id="3-整理成-package">3. 整理成 package</h3>
<p>核心原則：package 是一組 module 的命名空間。當多個 module 共同形成一個概念，就可以用資料夾與 <code>__init__.py</code> 整理成 package。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify_app/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   ├── __init__.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   ├── config.py
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   ├── parser.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">│   └── service.py
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── main.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>__init__.py</code> 的角色：初始化、公開 API、package 標記。</li>
<li>package 內部使用 absolute import 或 relative import 的取捨。</li>
<li>不要把所有名稱都重新 export 到 <code>__init__.py</code>。</li>
</ul>
<h3 id="4-執行方式會影響-import">4. 執行方式會影響 import</h3>
<p>核心原則：Python 的 import 行為和執行方式密切相關。<code>python main.py</code>、<code>python -m package.module</code>、測試工具與安裝後執行，可能會看到不同的 module search path。</p>
<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">python main.py
</span></span><span class="line"><span class="ln">2</span><span class="cl">python -m notify_app.cli
</span></span><span class="line"><span class="ln">3</span><span class="cl">python -m pytest</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>__name__</code> 與 <code>__package__</code> 的差異。</li>
<li>為什麼 package 內相對 import 在直接執行檔案時容易失敗。</li>
<li>CLI 入口和 library module 最好分開。</li>
</ul>
<h3 id="5-測試會推動專案結構">5. 測試會推動專案結構</h3>
<p>核心原則：當程式需要測試時，專案結構必須讓測試穩定 import 目標程式碼。測試是拆分 module 與 package 的重要壓力來源。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify_app/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   ├── __init__.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── service.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">    └── test_service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>tests/</code> 和 package 的相對位置。</li>
<li>為什麼測試常揭露 import 路徑設計不穩。</li>
<li>小型專案可以先維持簡單，大型或可發布專案再導入 <code>pyproject.toml</code>。</li>
</ul>
<h3 id="6-何時進入可安裝專案與-src-layout">6. 何時進入可安裝專案與 <code>src/</code> layout</h3>
<p>核心原則：<code>pyproject.toml</code> 與 <code>src/</code> layout 是正式專案管理工具，不是所有初學程式的起點。當專案需要被安裝、發布、由多個工具穩定執行時，再導入這些結構。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── notify_app/
</span></span><span class="line"><span class="ln">5</span><span class="cl">│       ├── __init__.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">│       └── service.py
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">8</span><span class="cl">    └── test_service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>src/</code> layout 防止「剛好從目前目錄 import 成功」的假象。</li>
<li>editable install 的用途。</li>
<li>進階打包內容應連到 <code>python-advanced/07-packaging/</code>。</li>
</ul>
<h2 id="後續補寫時的比較提示">後續補寫時的比較提示</h2>
<p>Python 和 Go 在這個主題上的差異應明確說清楚：</p>
<table>
  <thead>
      <tr>
          <th>主題</th>
          <th>Python</th>
          <th>Go</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最小單位</td>
          <td><code>.py</code> module</td>
          <td>package</td>
      </tr>
      <tr>
          <td>單檔起點</td>
          <td>任意 script</td>
          <td><code>package main</code> + <code>func main()</code></td>
      </tr>
      <tr>
          <td>可見性</td>
          <td><code>_name</code> 慣例，runtime 不強制</td>
          <td>大小寫由編譯器強制</td>
      </tr>
      <tr>
          <td>import 問題</td>
          <td>受 <code>sys.path</code>、執行方式、安裝方式影響</td>
          <td>受 <code>go.mod</code>、module path、package 邊界影響</td>
      </tr>
      <tr>
          <td>循環依賴</td>
          <td>runtime 才可能爆 partially initialized module</td>
          <td>編譯期拒絕 import cycle</td>
      </tr>
      <tr>
          <td>正式專案</td>
          <td><code>pyproject.toml</code>、package、<code>src/</code> layout</td>
          <td><code>go.mod</code>、package、<code>cmd/</code>、<code>internal/</code></td>
      </tr>
  </tbody>
</table>
<h2 id="本章不處理">本章不處理</h2>
<ul>
<li>不展開 packaging 與發布流程。</li>
<li>不深入 dependency management。</li>
<li>不討論大型框架專案結構。</li>
<li>不把 <code>src/</code> layout 當成所有專案的預設起點。</li>
</ul>
<h2 id="和-python-教材的關係">和 Python 教材的關係</h2>
<p>這一章承接的是 module、package 與測試路徑；如果你要先回看 Python 教材，可以讀：</p>
<ul>
<li><a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">Python：modules</a></li>
<li><a href="/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">Python 進階：packaging 與 distribution</a></li>
<li><a href="/blog/python/05-error-testing/" data-link-title="模組五：錯誤處理與測試" data-link-desc="穩健程式碼的基石：異常處理和單元測試">Python：測試基礎</a></li>
<li><a href="/blog/python/05-error-testing/mock/" data-link-title="5.4 Mock 與測試隔離" data-link-desc="隔離外部依賴">Python 進階：error handling 與 mock</a></li>
</ul>
<h2 id="小結">小結</h2>
<p>Python 程式的成長路線通常是 script、同層 module、package、可安裝專案。這條路線的核心是執行方式、import 邊界與測試方式逐步穩定。</p>
]]></content:encoded></item><item><title>1.2 變數、零值與短變數宣告</title><link>https://tarrragon.github.io/blog/go/01-basics/variables-zero-values/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/variables-zero-values/</guid><description>&lt;p>Go 變數宣告的核心規則是：每個變數都有明確型別，未指定初始值時會得到該型別的零值。本章將說明 &lt;code>var&lt;/code>、&lt;code>:=&lt;/code>、型別推斷與零值如何讓 Go 程式保持可預測。&lt;/p>
&lt;h2 id="變數一定有型別">變數一定有型別&lt;/h2>
&lt;p>Go 的變數一定屬於某個明確型別。這個型別可以由程式直接寫出，也可以由編譯器根據右側的初始值推斷出來；但推斷完成後，變數的型別就固定了。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;api&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="kd">var&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">8080&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="nx">enabled&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="nx">timeout&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">30&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>name&lt;/code> 的型別是 &lt;code>string&lt;/code>，&lt;code>port&lt;/code> 的型別是 &lt;code>int&lt;/code>，&lt;code>enabled&lt;/code> 與 &lt;code>timeout&lt;/code> 則由右側初始值推斷型別。Go 允許省略重複資訊，但不允許變數在執行期間任意改變型別。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">count&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">2&lt;/span>&lt;span class="cl">&lt;span class="nx">count&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">20&lt;/span> &lt;span class="c1">// 可以，仍然是 int&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">// count = &amp;#34;20&amp;#34; // 編譯錯誤：string 不能指派給 int&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個規則讓 Go 程式在閱讀時更穩定：看到一個變數後，讀者可以相信它的型別不會在後面突然變成另一種資料。&lt;/p>
&lt;h2 id="零值讓變數可立即使用">零值讓變數可立即使用&lt;/h2>
&lt;p>零值是 Go 對「尚未明確指定初始值」的標準答案。每個型別都有自己的零值：數字是 &lt;code>0&lt;/code>，字串是空字串，布林是 &lt;code>false&lt;/code>，指標、slice、map、function、interface 與 channel 是 &lt;code>nil&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">retryCount&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">title&lt;/span> &lt;span class="kt">string&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">var&lt;/span> &lt;span class="nx">debug&lt;/span> &lt;span class="kt">bool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">tags&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">retryCount&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 0&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">title&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// &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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">debug&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// false&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">tags&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// true&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>零值是型別的預設狀態。這是 Go 設計中很重要的精神：讓資料結構在最少初始化下仍然有合理行為。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Counter&lt;/span> &lt;span class="kd">struct&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">value&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">n&lt;/span> &lt;span class="kt">int&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="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nx">n&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Value&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">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="k">return&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&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="kd">var&lt;/span> &lt;span class="nx">counter&lt;/span> &lt;span class="nx">Counter&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">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">counter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Value&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1">// 3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>Counter&lt;/code> 沒有建構函式也能使用，因為 &lt;code>value&lt;/code> 的零值是 &lt;code>0&lt;/code>。當型別可以靠零值進入可用狀態時，使用者需要記住的初始化規則就會少很多。&lt;/p>
&lt;h2 id="var-適合宣告意圖">&lt;code>var&lt;/code> 適合宣告意圖&lt;/h2>
&lt;p>&lt;code>var&lt;/code> 的主要用途是清楚宣告變數的存在、型別或零值意義。當你需要讓讀者知道「這個變數稍後才會被賦值」或「零值本身有意義」時，&lt;code>var&lt;/code> 比 &lt;code>:=&lt;/code> 更清楚。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">userID&lt;/span> &lt;span class="kt">string&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">if&lt;/span> &lt;span class="nx">fromHeader&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>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">userID&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">fromHeader&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="k">else&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">userID&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">fromCookie&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;/code>&lt;/pre>&lt;/div>&lt;p>這裡的 &lt;code>userID&lt;/code> 需要經過條件判斷才會得到值，因此先用 &lt;code>var&lt;/code> 宣告變數，再在不同分支指派。讀者看到 &lt;code>var userID string&lt;/code>，可以理解這是一個稍後會被填入的字串。&lt;/p>
&lt;p>&lt;code>var&lt;/code> 也適合用在 package 層級，因為 package 層級不能使用 &lt;code>:=&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">defaultPort&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">serviceName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;worker&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>package 層級變數會增加全域狀態，使用前要先確認它是否真的代表整個 package 的共同狀態；如果只是函式內部暫存資料，應該放回函式裡。&lt;/p>
&lt;h2 id="-適合區域初始化">&lt;code>:=&lt;/code> 適合區域初始化&lt;/h2>
&lt;p>短變數宣告 &lt;code>:=&lt;/code> 的主要用途是在函式內同時宣告與初始化變數。當右側初始值已經清楚表達型別時，&lt;code>:=&lt;/code> 可以讓程式更精簡。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">greeting&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">string&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">message&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;hello, &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">name&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="nx">message&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;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>message&lt;/code> 的型別可以從字串串接結果推斷，所以不需要寫成 &lt;code>var message string = ...&lt;/code>。這種省略是移除讀者已經能從右側看懂的重複資訊。&lt;/p>
&lt;p>短變數宣告只能用在函式內。以下寫法不能放在 package 層級：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// package 層級不允許：&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">// port := 8080&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>:=&lt;/code> 也要求左側至少有一個新變數。這個規則會影響常見的錯誤處理寫法。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">loadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;config.json&amp;#34;&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">return&lt;/span> &lt;span class="nx">err&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="nx">config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">parseConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// config 是新變數，err 是重新指派&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="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nx">err&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;/code>&lt;/pre>&lt;/div>&lt;p>第二次使用 &lt;code>:=&lt;/code> 是合法的，因為 &lt;code>config&lt;/code> 是新變數；同時 &lt;code>err&lt;/code> 會被重新指派。這種寫法在 Go 很常見，但要留意不要在內層區塊意外宣告出新的同名變數。&lt;/p>
&lt;h2 id="型別推斷不等於放棄型別">型別推斷不等於放棄型別&lt;/h2>
&lt;p>型別推斷的核心作用是減少重複，不是讓變數變成動態型別。Go 只在編譯期根據右側表達式推斷型別，推斷後仍然遵守靜態型別規則。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">limit&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">100&lt;/span> &lt;span class="c1">// int&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">ratio&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mf">0.75&lt;/span> &lt;span class="c1">// float64&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">label&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="s">&amp;#34;active&amp;#34;&lt;/span> &lt;span class="c1">// string&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-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">timeoutSeconds&lt;/span> &lt;span class="kt">int64&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">threshold&lt;/span> &lt;span class="kt">float32&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">0.8&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這裡如果只寫 &lt;code>timeoutSeconds := 30&lt;/code>，通常會得到 &lt;code>int&lt;/code>；如果 API、資料庫欄位或二進位格式需要 &lt;code>int64&lt;/code>，明確宣告型別會比事後轉型更好讀。&lt;/p>
&lt;h2 id="指標slice-與-map-的零值差異">指標、slice 與 map 的零值差異&lt;/h2>
&lt;p>零值的共同規則是「未初始化時有定義好的狀態」，但不同型別的零值可用程度不同。指標的零值是 &lt;code>nil&lt;/code>，使用前需要確認是否指向有效資料；slice 的零值可以安全讀長度與 append；map 的零值可以讀取但不能寫入。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">names&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&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">names&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">names&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;alice&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">names&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">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="kd">var&lt;/span> &lt;span class="nx">scores&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">int&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">scores&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;alice&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1">// 0&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">// scores[&amp;#34;alice&amp;#34;] = 10 // panic: assignment to entry in nil map&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>slice 的零值能直接 &lt;code>append&lt;/code>，所以很多函式可以回傳 &lt;code>nil&lt;/code> slice 表示沒有資料；呼叫端仍然可以用 &lt;code>len&lt;/code> 與 &lt;code>range&lt;/code> 處理。map 要寫入前必須先用 &lt;code>make&lt;/code> 初始化。&lt;/p></description><content:encoded><![CDATA[<p>Go 變數宣告的核心規則是：每個變數都有明確型別，未指定初始值時會得到該型別的零值。本章將說明 <code>var</code>、<code>:=</code>、型別推斷與零值如何讓 Go 程式保持可預測。</p>
<h2 id="變數一定有型別">變數一定有型別</h2>
<p>Go 的變數一定屬於某個明確型別。這個型別可以由程式直接寫出，也可以由編譯器根據右側的初始值推斷出來；但推斷完成後，變數的型別就固定了。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">name</span> <span class="kt">string</span> <span class="p">=</span> <span class="s">&#34;api&#34;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">var</span> <span class="nx">port</span> <span class="kt">int</span> <span class="p">=</span> <span class="mi">8080</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="nx">enabled</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="nx">timeout</span> <span class="o">:=</span> <span class="mi">30</span></span></span></code></pre></div><p><code>name</code> 的型別是 <code>string</code>，<code>port</code> 的型別是 <code>int</code>，<code>enabled</code> 與 <code>timeout</code> 則由右側初始值推斷型別。Go 允許省略重複資訊，但不允許變數在執行期間任意改變型別。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">count</span> <span class="o">:=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">count</span> <span class="p">=</span> <span class="mi">20</span>      <span class="c1">// 可以，仍然是 int</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// count = &#34;20&#34; // 編譯錯誤：string 不能指派給 int</span></span></span></code></pre></div><p>這個規則讓 Go 程式在閱讀時更穩定：看到一個變數後，讀者可以相信它的型別不會在後面突然變成另一種資料。</p>
<h2 id="零值讓變數可立即使用">零值讓變數可立即使用</h2>
<p>零值是 Go 對「尚未明確指定初始值」的標準答案。每個型別都有自己的零值：數字是 <code>0</code>，字串是空字串，布林是 <code>false</code>，指標、slice、map、function、interface 與 channel 是 <code>nil</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">retryCount</span> <span class="kt">int</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">var</span> <span class="nx">title</span> <span class="kt">string</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">var</span> <span class="nx">debug</span> <span class="kt">bool</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">var</span> <span class="nx">tags</span> <span class="p">[]</span><span class="kt">string</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="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">retryCount</span><span class="p">)</span> <span class="c1">// 0</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">title</span><span class="p">)</span>      <span class="c1">// &#34;&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">debug</span><span class="p">)</span>      <span class="c1">// false</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">tags</span> <span class="o">==</span> <span class="kc">nil</span><span class="p">)</span> <span class="c1">// true</span></span></span></code></pre></div><p>零值是型別的預設狀態。這是 Go 設計中很重要的精神：讓資料結構在最少初始化下仍然有合理行為。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">Counter</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">value</span> <span class="kt">int</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Counter</span><span class="p">)</span> <span class="nf">Add</span><span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">c</span><span class="p">.</span><span class="nx">value</span> <span class="o">+=</span> <span class="nx">n</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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="nx">Counter</span><span class="p">)</span> <span class="nf">Value</span><span class="p">()</span> <span class="kt">int</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="nx">c</span><span class="p">.</span><span class="nx">value</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="kd">var</span> <span class="nx">counter</span> <span class="nx">Counter</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">counter</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">counter</span><span class="p">.</span><span class="nf">Value</span><span class="p">())</span> <span class="c1">// 3</span></span></span></code></pre></div><p><code>Counter</code> 沒有建構函式也能使用，因為 <code>value</code> 的零值是 <code>0</code>。當型別可以靠零值進入可用狀態時，使用者需要記住的初始化規則就會少很多。</p>
<h2 id="var-適合宣告意圖"><code>var</code> 適合宣告意圖</h2>
<p><code>var</code> 的主要用途是清楚宣告變數的存在、型別或零值意義。當你需要讓讀者知道「這個變數稍後才會被賦值」或「零值本身有意義」時，<code>var</code> 比 <code>:=</code> 更清楚。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">userID</span> <span class="kt">string</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">if</span> <span class="nx">fromHeader</span> <span class="o">!=</span> <span class="s">&#34;&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">userID</span> <span class="p">=</span> <span class="nx">fromHeader</span>
</span></span><span class="line"><span class="ln">5</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">6</span><span class="cl">    <span class="nx">userID</span> <span class="p">=</span> <span class="nx">fromCookie</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這裡的 <code>userID</code> 需要經過條件判斷才會得到值，因此先用 <code>var</code> 宣告變數，再在不同分支指派。讀者看到 <code>var userID string</code>，可以理解這是一個稍後會被填入的字串。</p>
<p><code>var</code> 也適合用在 package 層級，因為 package 層級不能使用 <code>:=</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">defaultPort</span> <span class="p">=</span> <span class="mi">8080</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">var</span> <span class="nx">serviceName</span> <span class="p">=</span> <span class="s">&#34;worker&#34;</span></span></span></code></pre></div><p>package 層級變數會增加全域狀態，使用前要先確認它是否真的代表整個 package 的共同狀態；如果只是函式內部暫存資料，應該放回函式裡。</p>
<h2 id="-適合區域初始化"><code>:=</code> 適合區域初始化</h2>
<p>短變數宣告 <code>:=</code> 的主要用途是在函式內同時宣告與初始化變數。當右側初始值已經清楚表達型別時，<code>:=</code> 可以讓程式更精簡。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">greeting</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">message</span> <span class="o">:=</span> <span class="s">&#34;hello, &#34;</span> <span class="o">+</span> <span class="nx">name</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="nx">message</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>message</code> 的型別可以從字串串接結果推斷，所以不需要寫成 <code>var message string = ...</code>。這種省略是移除讀者已經能從右側看懂的重複資訊。</p>
<p>短變數宣告只能用在函式內。以下寫法不能放在 package 層級：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// package 層級不允許：</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// port := 8080</span></span></span></code></pre></div><p><code>:=</code> 也要求左側至少有一個新變數。這個規則會影響常見的錯誤處理寫法。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">data</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">loadFile</span><span class="p">(</span><span class="s">&#34;config.json&#34;</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="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="nx">err</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="nx">config</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">parseConfig</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="c1">// config 是新變數，err 是重新指派</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</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="nx">err</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>第二次使用 <code>:=</code> 是合法的，因為 <code>config</code> 是新變數；同時 <code>err</code> 會被重新指派。這種寫法在 Go 很常見，但要留意不要在內層區塊意外宣告出新的同名變數。</p>
<h2 id="型別推斷不等於放棄型別">型別推斷不等於放棄型別</h2>
<p>型別推斷的核心作用是減少重複，不是讓變數變成動態型別。Go 只在編譯期根據右側表達式推斷型別，推斷後仍然遵守靜態型別規則。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">limit</span> <span class="o">:=</span> <span class="mi">100</span>       <span class="c1">// int</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">ratio</span> <span class="o">:=</span> <span class="mf">0.75</span>      <span class="c1">// float64</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">label</span> <span class="o">:=</span> <span class="s">&#34;active&#34;</span>  <span class="c1">// string</span></span></span></code></pre></div><p>當右側型別不夠明確，或你需要指定更精確的型別時，就應該直接寫出型別。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">timeoutSeconds</span> <span class="kt">int64</span> <span class="p">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">var</span> <span class="nx">threshold</span> <span class="kt">float32</span> <span class="p">=</span> <span class="mf">0.8</span></span></span></code></pre></div><p>這裡如果只寫 <code>timeoutSeconds := 30</code>，通常會得到 <code>int</code>；如果 API、資料庫欄位或二進位格式需要 <code>int64</code>，明確宣告型別會比事後轉型更好讀。</p>
<h2 id="指標slice-與-map-的零值差異">指標、slice 與 map 的零值差異</h2>
<p>零值的共同規則是「未初始化時有定義好的狀態」，但不同型別的零值可用程度不同。指標的零值是 <code>nil</code>，使用前需要確認是否指向有效資料；slice 的零值可以安全讀長度與 append；map 的零值可以讀取但不能寫入。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">names</span> <span class="p">[]</span><span class="kt">string</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">names</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">names</span><span class="p">,</span> <span class="s">&#34;alice&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">names</span><span class="p">))</span> <span class="c1">// 1</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="kd">var</span> <span class="nx">scores</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">scores</span><span class="p">[</span><span class="s">&#34;alice&#34;</span><span class="p">])</span> <span class="c1">// 0</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// scores[&#34;alice&#34;] = 10      // panic: assignment to entry in nil map</span></span></span></code></pre></div><p>slice 的零值能直接 <code>append</code>，所以很多函式可以回傳 <code>nil</code> slice 表示沒有資料；呼叫端仍然可以用 <code>len</code> 與 <code>range</code> 處理。map 要寫入前必須先用 <code>make</code> 初始化。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">scores</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">scores</span><span class="p">[</span><span class="s">&#34;alice&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="mi">10</span></span></span></code></pre></div><p>這個差異是零值判讀的核心陷阱：<code>nil</code> slice 通常容易處理，<code>nil</code> map 則需要先初始化才能寫入。</p>
<h2 id="命名要服務讀者">命名要服務讀者</h2>
<p>變數名稱的核心責任是說明資料在當前範圍內的角色。範圍越小，名稱可以越短；範圍越大，名稱就應該越具體。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">for</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">item</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">items</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>i</code> 在很短的迴圈內代表 index，讀者可以立即理解。相反地，跨越多個段落使用的變數應該使用更完整的名稱。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">requestTimeout</span> <span class="o">:=</span> <span class="mi">5</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">maxRetryCount</span> <span class="o">:=</span> <span class="mi">3</span></span></span></code></pre></div><p>Go 程式常使用短名稱，但短名稱不是目標本身。好的名稱應該讓讀者不必回頭搜尋變數來源，就能理解這個值現在代表什麼。</p>
<h2 id="下一章">下一章</h2>
<p>下一章會進入控制流程，說明 Go 如何用少量語法表達條件、迴圈與多分支判斷。</p>
]]></content:encoded></item><item><title>1.3 控制流程：if、for、switch</title><link>https://tarrragon.github.io/blog/go/01-basics/control-flow/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/control-flow/</guid><description>&lt;p>Go 控制流程的核心規則是：語法少但語意明確；&lt;code>if&lt;/code> 處理條件分支，&lt;code>for&lt;/code> 是唯一迴圈語法，&lt;code>switch&lt;/code> 用於多分支判斷。本章將建立閱讀 Go 流程控制的基本模型。&lt;/p>
&lt;h2 id="if-表達條件與提前返回">&lt;code>if&lt;/code> 表達條件與提前返回&lt;/h2>
&lt;p>&lt;code>if&lt;/code> 的核心責任是根據條件決定程式是否進入某段邏輯。Go 的 &lt;code>if&lt;/code> 條件不需要小括號，但區塊大括號是必要語法。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="nx">age&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">18&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;adult&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Go 不會把數字、字串或指標自動當成布林值。條件必須是明確的 &lt;code>bool&lt;/code> 表達式。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">count&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">3&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">if&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="p">&amp;gt;&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">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;has items&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="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">// if count {&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">// fmt.Println(&amp;#34;invalid&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="c1">// }&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個規則讓條件判斷更直接：讀者不需要猜某個非布林值在條件中會被如何轉換。&lt;/p>
&lt;h2 id="if-可以包含短宣告">&lt;code>if&lt;/code> 可以包含短宣告&lt;/h2>
&lt;p>&lt;code>if&lt;/code> 的短宣告用來把只屬於這個判斷的暫存變數限制在區塊內。這讓錯誤處理與查找結果的作用範圍更清楚。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="nx">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;user:1&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="nx">ok&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;cache hit:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>value&lt;/code> 與 &lt;code>ok&lt;/code> 只存在於 &lt;code>if&lt;/code> 與對應的 &lt;code>else&lt;/code> 區塊內。這種寫法適合處理 map 查找、型別轉換、函式呼叫錯誤等短生命週期資料。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&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="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">saveProfile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">profile&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&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>err&lt;/code> 只用來判斷 &lt;code>saveProfile&lt;/code> 是否失敗，離開 &lt;code>if&lt;/code> 後就不再需要。短宣告可以降低變數留在外層範圍造成的干擾。&lt;/p>
&lt;h2 id="提前返回讓主流程靠左">提前返回讓主流程靠左&lt;/h2>
&lt;p>Go 常用提前返回處理失敗或特殊情況。核心原則是先處理不能繼續的狀態，讓正常流程留在較少縮排的位置。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">normalizeEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&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"> 2&lt;/span>&lt;span class="cl"> &lt;span class="nx">input&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">TrimSpace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">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="nx">input&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>&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="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;email is required&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="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">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&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"> 8&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 class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;invalid email&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="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="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ToLower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式先排除空字串與格式錯誤，最後才回傳正常結果。讀者可以依序看到「不能接受什麼」以及「通過檢查後會得到什麼」。&lt;/p>
&lt;p>提前返回不是要求每個條件都拆開。當兩個條件代表同一個規則時，可以合併成一個判斷；當條件代表不同失敗原因時，拆開通常比較清楚。&lt;/p>
&lt;h2 id="for-是唯一迴圈語法">&lt;code>for&lt;/code> 是唯一迴圈語法&lt;/h2>
&lt;p>Go 的迴圈只有 &lt;code>for&lt;/code>。它可以表達傳統計數迴圈、條件迴圈、無限迴圈與 range 迴圈。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&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">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是傳統的三段式迴圈：初始化、條件、迭代後處理。它適合需要 index、固定次數或精準控制遞增方式的場景。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">remaining&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">3&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">for&lt;/span> &lt;span class="nx">remaining&lt;/span> &lt;span class="p">&amp;gt;&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">remaining&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">remaining&lt;/span>&lt;span class="o">--&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>省略初始化與後處理後，&lt;code>for&lt;/code> 就是其他語言常見的 while 迴圈。Go 不另外提供 &lt;code>while&lt;/code>，因為 &lt;code>for 條件&lt;/code> 已經能表達同一件事。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">for&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;polling&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="k">break&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;/code>&lt;/pre>&lt;/div>&lt;p>沒有條件的 &lt;code>for&lt;/code> 是無限迴圈，通常會搭配 &lt;code>break&lt;/code>、&lt;code>return&lt;/code>、&lt;code>context&lt;/code> 或 channel 退出。無限迴圈要讓退出條件清楚可見，否則很容易讓讀者無法判斷生命週期。&lt;/p>
&lt;h2 id="range-用來走訪集合">&lt;code>range&lt;/code> 用來走訪集合&lt;/h2>
&lt;p>&lt;code>range&lt;/code> 的核心用途是逐一走訪陣列、slice、map、字串與 channel。它會依資料型別產生不同的索引或值。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">names&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s">&amp;#34;alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;bob&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">names&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&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>走訪 slice 時，第一個值是 index，第二個值是元素副本。若不需要 index，可以用 &lt;code>_&lt;/code> 忽略。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">name&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">names&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>走訪 map 時，順序沒有保證。這是語言刻意設計的結果，避免程式誤以為 map 有穩定順序。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">scores&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">int&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="s">&amp;#34;alice&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">90&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="s">&amp;#34;bob&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">80&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="k">for&lt;/span> &lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">score&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">scores&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">score&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;/code>&lt;/pre>&lt;/div>&lt;p>如果輸出順序重要，應該先取出 key、排序，再依序讀取 map。&lt;/p>
&lt;h2 id="break-與-continue-控制迴圈節奏">&lt;code>break&lt;/code> 與 &lt;code>continue&lt;/code> 控制迴圈節奏&lt;/h2>
&lt;p>&lt;code>break&lt;/code> 的核心作用是結束目前迴圈，&lt;code>continue&lt;/code> 的核心作用是跳過本次迭代並進入下一輪。它們應該用來表達清楚的流程轉折，而不是補救過度複雜的迴圈。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">line&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">lines&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="nx">line&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&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"> 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="k">if&lt;/span> &lt;span class="nx">line&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s">&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">break&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">line&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;/code>&lt;/pre>&lt;/div>&lt;p>這段程式忽略空行，遇到 &lt;code>STOP&lt;/code> 停止，其他行則輸出。條件都放在處理邏輯前方，讀者可以先理解哪些資料不進入主流程。&lt;/p>
&lt;p>當迴圈內的 &lt;code>break&lt;/code>、&lt;code>continue&lt;/code>、巢狀條件太多時，通常代表應該把部分邏輯抽成函式，讓每個函式只負責一層判斷。&lt;/p>
&lt;h2 id="switch-表達多分支判斷">&lt;code>switch&lt;/code> 表達多分支判斷&lt;/h2>
&lt;p>&lt;code>switch&lt;/code> 的核心責任是把同一個概念的多種可能集中呈現。Go 的 &lt;code>switch&lt;/code> 預設不會自動落入下一個 &lt;code>case&lt;/code>，所以大多數情況不需要寫 &lt;code>break&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">switch&lt;/span> &lt;span class="nx">method&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">case&lt;/span> &lt;span class="s">&amp;#34;GET&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;read&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">case&lt;/span> &lt;span class="s">&amp;#34;POST&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;create&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">case&lt;/span> &lt;span class="s">&amp;#34;DELETE&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;delete&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">default&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;unsupported&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;/code>&lt;/pre>&lt;/div>&lt;p>每個 &lt;code>case&lt;/code> 預設只執行自己的區塊。若真的需要落入下一個 case，Go 提供 &lt;code>fallthrough&lt;/code>，但日常程式很少需要它。&lt;/p></description><content:encoded><![CDATA[<p>Go 控制流程的核心規則是：語法少但語意明確；<code>if</code> 處理條件分支，<code>for</code> 是唯一迴圈語法，<code>switch</code> 用於多分支判斷。本章將建立閱讀 Go 流程控制的基本模型。</p>
<h2 id="if-表達條件與提前返回"><code>if</code> 表達條件與提前返回</h2>
<p><code>if</code> 的核心責任是根據條件決定程式是否進入某段邏輯。Go 的 <code>if</code> 條件不需要小括號，但區塊大括號是必要語法。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="nx">age</span> <span class="o">&gt;=</span> <span class="mi">18</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;adult&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Go 不會把數字、字串或指標自動當成布林值。條件必須是明確的 <code>bool</code> 表達式。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">count</span> <span class="o">:=</span> <span class="mi">3</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">if</span> <span class="nx">count</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;has items&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// if count {</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">//     fmt.Println(&#34;invalid&#34;)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1">// }</span></span></span></code></pre></div><p>這個規則讓條件判斷更直接：讀者不需要猜某個非布林值在條件中會被如何轉換。</p>
<h2 id="if-可以包含短宣告"><code>if</code> 可以包含短宣告</h2>
<p><code>if</code> 的短宣告用來把只屬於這個判斷的暫存變數限制在區塊內。這讓錯誤處理與查找結果的作用範圍更清楚。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="nx">value</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">cache</span><span class="p">[</span><span class="s">&#34;user:1&#34;</span><span class="p">];</span> <span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;cache hit:&#34;</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>value</code> 與 <code>ok</code> 只存在於 <code>if</code> 與對應的 <code>else</code> 區塊內。這種寫法適合處理 map 查找、型別轉換、函式呼叫錯誤等短生命週期資料。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">saveProfile</span><span class="p">(</span><span class="nx">profile</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這裡的 <code>err</code> 只用來判斷 <code>saveProfile</code> 是否失敗，離開 <code>if</code> 後就不再需要。短宣告可以降低變數留在外層範圍造成的干擾。</p>
<h2 id="提前返回讓主流程靠左">提前返回讓主流程靠左</h2>
<p>Go 常用提前返回處理失敗或特殊情況。核心原則是先處理不能繼續的狀態，讓正常流程留在較少縮排的位置。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="nf">normalizeEmail</span><span class="p">(</span><span class="nx">input</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">input</span> <span class="p">=</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSpace</span><span class="p">(</span><span class="nx">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="nx">input</span> <span class="o">==</span> <span class="s">&#34;&#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="s">&#34;&#34;</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;email is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="p">!</span><span class="nx">strings</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nx">input</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"> 8</span><span class="cl">        <span class="k">return</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;invalid email&#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></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nx">input</span><span class="p">),</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這段程式先排除空字串與格式錯誤，最後才回傳正常結果。讀者可以依序看到「不能接受什麼」以及「通過檢查後會得到什麼」。</p>
<p>提前返回不是要求每個條件都拆開。當兩個條件代表同一個規則時，可以合併成一個判斷；當條件代表不同失敗原因時，拆開通常比較清楚。</p>
<h2 id="for-是唯一迴圈語法"><code>for</code> 是唯一迴圈語法</h2>
<p>Go 的迴圈只有 <code>for</code>。它可以表達傳統計數迴圈、條件迴圈、無限迴圈與 range 迴圈。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這是傳統的三段式迴圈：初始化、條件、迭代後處理。它適合需要 index、固定次數或精準控制遞增方式的場景。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">remaining</span> <span class="o">:=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="nx">remaining</span> <span class="p">&gt;</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="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">remaining</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">remaining</span><span class="o">--</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>省略初始化與後處理後，<code>for</code> 就是其他語言常見的 while 迴圈。Go 不另外提供 <code>while</code>，因為 <code>for 條件</code> 已經能表達同一件事。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;polling&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">break</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>沒有條件的 <code>for</code> 是無限迴圈，通常會搭配 <code>break</code>、<code>return</code>、<code>context</code> 或 channel 退出。無限迴圈要讓退出條件清楚可見，否則很容易讓讀者無法判斷生命週期。</p>
<h2 id="range-用來走訪集合"><code>range</code> 用來走訪集合</h2>
<p><code>range</code> 的核心用途是逐一走訪陣列、slice、map、字串與 channel。它會依資料型別產生不同的索引或值。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">names</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;alice&#34;</span><span class="p">,</span> <span class="s">&#34;bob&#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="k">for</span> <span class="nx">i</span><span class="p">,</span> <span class="nx">name</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">names</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">name</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>走訪 slice 時，第一個值是 index，第二個值是元素副本。若不需要 index，可以用 <code>_</code> 忽略。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">name</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">names</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>走訪 map 時，順序沒有保證。這是語言刻意設計的結果，避免程式誤以為 map 有穩定順序。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">scores</span> <span class="o">:=</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s">&#34;alice&#34;</span><span class="p">:</span> <span class="mi">90</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s">&#34;bob&#34;</span><span class="p">:</span>   <span class="mi">80</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="k">for</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">score</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">scores</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">score</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><p>如果輸出順序重要，應該先取出 key、排序，再依序讀取 map。</p>
<h2 id="break-與-continue-控制迴圈節奏"><code>break</code> 與 <code>continue</code> 控制迴圈節奏</h2>
<p><code>break</code> 的核心作用是結束目前迴圈，<code>continue</code> 的核心作用是跳過本次迭代並進入下一輪。它們應該用來表達清楚的流程轉折，而不是補救過度複雜的迴圈。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">line</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">lines</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="nx">line</span> <span class="o">==</span> <span class="s">&#34;&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">continue</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="k">if</span> <span class="nx">line</span> <span class="o">==</span> <span class="s">&#34;STOP&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">break</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="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">line</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這段程式忽略空行，遇到 <code>STOP</code> 停止，其他行則輸出。條件都放在處理邏輯前方，讀者可以先理解哪些資料不進入主流程。</p>
<p>當迴圈內的 <code>break</code>、<code>continue</code>、巢狀條件太多時，通常代表應該把部分邏輯抽成函式，讓每個函式只負責一層判斷。</p>
<h2 id="switch-表達多分支判斷"><code>switch</code> 表達多分支判斷</h2>
<p><code>switch</code> 的核心責任是把同一個概念的多種可能集中呈現。Go 的 <code>switch</code> 預設不會自動落入下一個 <code>case</code>，所以大多數情況不需要寫 <code>break</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">switch</span> <span class="nx">method</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">case</span> <span class="s">&#34;GET&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;read&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">case</span> <span class="s">&#34;POST&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;create&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">case</span> <span class="s">&#34;DELETE&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;unsupported&#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></code></pre></div><p>每個 <code>case</code> 預設只執行自己的區塊。若真的需要落入下一個 case，Go 提供 <code>fallthrough</code>，但日常程式很少需要它。</p>
<p><code>switch</code> 也可以不帶目標值，用來取代一長串 <code>if else</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">switch</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">case</span> <span class="nx">score</span> <span class="o">&gt;=</span> <span class="mi">90</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;A&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">case</span> <span class="nx">score</span> <span class="o">&gt;=</span> <span class="mi">80</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;B&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">case</span> <span class="nx">score</span> <span class="o">&gt;=</span> <span class="mi">70</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;C&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;D&#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></code></pre></div><p>這種寫法適合條件都在描述同一個分類規則時使用。若每個條件都在處理不同概念，拆成多個 <code>if</code> 或不同函式通常更清楚。</p>
<h2 id="下一章">下一章</h2>
<p>下一章會回到 package 與檔案組織，說明 Go 如何用 package 建立程式邊界。</p>
]]></content:encoded></item><item><title>1.3 模組與套件組織</title><link>https://tarrragon.github.io/blog/python/01-basics/modules/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/modules/</guid><description>&lt;p>Python 的模組系統是組織程式碼的基礎。理解模組如何運作，是維護和擴展 Hook 系統的關鍵。&lt;/p>
&lt;blockquote>
&lt;p>承接提示：如果你還不熟悉程式如何從單一 &lt;code>.py&lt;/code> 檔案拆成多個 module，請先閱讀 &lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案&lt;/a>。本章聚焦在已經出現 package 後，如何理解 module、&lt;code>__init__.py&lt;/code> 與公開 API。&lt;/p>&lt;/blockquote>
&lt;h2 id="基本概念">基本概念&lt;/h2>
&lt;h3 id="模組module">模組（Module）&lt;/h3>
&lt;p>一個 &lt;code>.py&lt;/code> 檔案就是一個模組。例如 &lt;code>git_utils.py&lt;/code> 就是 &lt;code>git_utils&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"># git_utils.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="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="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="套件package">套件（Package）&lt;/h3>
&lt;p>包含 &lt;code>__init__.py&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 # 使 lib 成為套件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── git_utils.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── hook_io.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── hook_logging.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">└── config_loader.py # 模組&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="__init__py-的作用">&lt;code>__init__.py&lt;/code> 的作用&lt;/h2>
&lt;p>&lt;code>__init__.py&lt;/code> 是套件的初始化檔案，它在套件被導入時執行。&lt;/p>
&lt;h3 id="實際範例hook-系統的-__init__py">實際範例：Hook 系統的 &lt;code>__init__.py&lt;/code>&lt;/h3>
&lt;p>來自 &lt;code>.claude/lib/__init__.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="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">Claude Hooks 共用程式庫
&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">提供 Hook 腳本共用的工具函式，消除程式碼重複。
&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">模組結構:
&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">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
&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_logging: 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_io: 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">&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="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="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="n">is_protected_branch&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_allowed_branch&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="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="kn">from&lt;/span> &lt;span class="nn">.hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&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="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">25&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">26&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">27&lt;/span>&lt;span class="cl"> &lt;span class="n">create_pretooluse_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="n">create_posttooluse_output&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>&lt;/span>&lt;span class="line">&lt;span class="ln">31&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">32&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">33&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">34&lt;/span>&lt;span class="cl"> &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">35&lt;/span>&lt;span class="cl"> &lt;span class="n">clear_config_cache&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"># 定義公開 API&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">__all__&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="c1"># git_utils&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;run_git_command&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="s2">&amp;#34;get_current_branch&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="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="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="n">__version__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.28.0&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="__init__py-的三個主要功能">&lt;code>__init__.py&lt;/code> 的三個主要功能&lt;/h3>
&lt;h4 id="1-宣告套件身份">1. 宣告套件身份&lt;/h4>
&lt;p>空的 &lt;code>__init__.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"># 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="c1"># 即使是空檔案，也使 lib 成為套件&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="2-定義公開-api">2. 定義公開 API&lt;/h4>
&lt;p>透過 &lt;code>__all__&lt;/code> 列表控制 &lt;code>from package import *&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="n">__all__&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">2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;run_git_command&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="s2">&amp;#34;get_current_branch&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="s2">&amp;#34;setup_hook_logging&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="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"># 當使用者執行 from lib import * 時&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"># 只會導入 __all__ 中列出的名稱&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="3-簡化導入路徑">3. 簡化導入路徑&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="c1"># 有 __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="kn">from&lt;/span> &lt;span class="nn">lib&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">setup_hook_logging&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"># 沒有 __init__.py 的重新匯出：冗長&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&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>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lib.hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="相對導入與絕對導入">相對導入與絕對導入&lt;/h2>
&lt;h3 id="相對導入使用-">相對導入（使用 &lt;code>.&lt;/code>）&lt;/h3>
&lt;p>在套件內部使用相對導入：&lt;/p></description><content:encoded><![CDATA[<p>Python 的模組系統是組織程式碼的基礎。理解模組如何運作，是維護和擴展 Hook 系統的關鍵。</p>
<blockquote>
<p>承接提示：如果你還不熟悉程式如何從單一 <code>.py</code> 檔案拆成多個 module，請先閱讀 <a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案</a>。本章聚焦在已經出現 package 後，如何理解 module、<code>__init__.py</code> 與公開 API。</p></blockquote>
<h2 id="基本概念">基本概念</h2>
<h3 id="模組module">模組（Module）</h3>
<p>一個 <code>.py</code> 檔案就是一個模組。例如 <code>git_utils.py</code> 就是 <code>git_utils</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"># git_utils.py 是一個模組</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="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span></span></span></code></pre></div><h3 id="套件package">套件（Package）</h3>
<p>包含 <code>__init__.py</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      # 使 lib 成為套件
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── git_utils.py     # 模組
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── hook_io.py       # 模組
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── hook_logging.py  # 模組
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── config_loader.py # 模組</span></span></code></pre></div><h2 id="__init__py-的作用"><code>__init__.py</code> 的作用</h2>
<p><code>__init__.py</code> 是套件的初始化檔案，它在套件被導入時執行。</p>
<h3 id="實際範例hook-系統的-__init__py">實際範例：Hook 系統的 <code>__init__.py</code></h3>
<p>來自 <code>.claude/lib/__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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">Claude Hooks 共用程式庫
</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">提供 Hook 腳本共用的工具函式，消除程式碼重複。
</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">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">- hook_logging: Hook 日誌系統
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">- hook_io: Hook 輸入輸出處理
</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="c1"># 從子模組導入並重新匯出</span>
</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="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">is_allowed_branch</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="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">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</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">25</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">create_posttooluse_output</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></span><span class="line"><span class="ln">31</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">32</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">load_quality_rules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">clear_config_cache</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"># 定義公開 API</span>
</span></span><span class="line"><span class="ln">39</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">40</span><span class="cl">    <span class="c1"># git_utils</span>
</span></span><span class="line"><span class="ln">41</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">42</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">43</span><span class="cl">    <span class="c1"># ... 省略其他</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="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.28.0&#34;</span></span></span></code></pre></div><h3 id="__init__py-的三個主要功能"><code>__init__.py</code> 的三個主要功能</h3>
<h4 id="1-宣告套件身份">1. 宣告套件身份</h4>
<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"># lib/__init__.py（最簡形式）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 即使是空檔案，也使 lib 成為套件</span></span></span></code></pre></div><h4 id="2-定義公開-api">2. 定義公開 API</h4>
<p>透過 <code>__all__</code> 列表控制 <code>from package import *</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">__all__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">2</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">3</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">4</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">5</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 當使用者執行 from lib import * 時</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 只會導入 __all__ 中列出的名稱</span></span></span></code></pre></div><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"># 有 __init__.py 的重新匯出：簡潔</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">setup_hook_logging</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"># 沒有 __init__.py 的重新匯出：冗長</span>
</span></span><span class="line"><span class="ln">5</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></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span></span></span></code></pre></div><h2 id="相對導入與絕對導入">相對導入與絕對導入</h2>
<h3 id="相對導入使用-">相對導入（使用 <code>.</code>）</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"># 在 lib/config_loader.py 中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="n">get_project_root</span>  <span class="c1"># 同級模組</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">hook_io</span>                     <span class="c1"># 導入整個模組</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="c1"># 在 .claude/hooks/some_hook.py 中</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 class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>  <span class="c1"># 絕對導入</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h2 id="套件版本管理">套件版本管理</h2>
<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="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.28.0&#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="kn">import</span> <span class="nn">lib</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">__version__</span><span class="p">)</span>  <span class="c1"># &#34;0.28.0&#34;</span></span></span></code></pre></div><h2 id="模組載入順序">模組載入順序</h2>
<p>Python 搜尋模組的順序：</p>
<ol>
<li>內建模組</li>
<li><code>sys.path[0]</code>（腳本所在目錄）</li>
<li><code>PYTHONPATH</code> 環境變數</li>
<li>標準庫</li>
<li>site-packages（第三方套件）</li>
</ol>
<h3 id="實際範例hook-腳本的路徑設定">實際範例：Hook 腳本的路徑設定</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;Branch Verify 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="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 將 lib 目錄加入搜尋路徑</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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"># 現在可以導入 lib 中的模組</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span></span></span></code></pre></div><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="c1"># 不好：a.py 和 b.py 互相導入</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># a.py</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">b</span> <span class="kn">import</span> <span class="n">func_b</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># b.py</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">from</span> <span class="nn">a</span> <span class="kn">import</span> <span class="n">func_a</span>  <span class="c1"># 循環導入！</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"># common.py</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">shared_function</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># a.py</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">common</span> <span class="kn">import</span> <span class="n">shared_function</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># b.py</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">common</span> <span class="kn">import</span> <span class="n">shared_function</span></span></span></code></pre></div><h3 id="2-延遲導入">2. 延遲導入</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">def</span> <span class="nf">load_yaml_config</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">5</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="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</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">7</span><span class="cl">        <span class="c1"># 備案方案</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="kn">import</span> <span class="nn">json</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="o">...</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-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">lib/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── git_utils.py      # Git 操作（單一職責）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── hook_io.py        # 輸入輸出處理（單一職責）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── hook_logging.py   # 日誌系統（單一職責）
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── config_loader.py  # 配置載入（單一職責）</span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>__init__.py</code> 使用 <code>from .git_utils import ...</code> 而不是 <code>from git_utils import ...</code>？</li>
<li>Hook 腳本中的 <code>sys.path.insert(0, ...)</code> 為什麼使用 <code>0</code> 作為索引？</li>
<li><code>__all__</code> 列表的作用是什麼？如果不定義會怎樣？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<p>閱讀 <code>.claude/lib/__init__.py</code>，回答以下問題：</p>
<ol>
<li>這個套件匯出了多少個公開函式？</li>
<li>套件的版本號是多少？</li>
<li>如果要新增一個 <code>utils.py</code> 模組並匯出 <code>format_message</code> 函式，需要修改哪些地方？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">Python 哲學與設計理念</a></em>
<em>下一章：<a href="/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">導入機制與路徑管理</a></em></p>
]]></content:encoded></item><item><title>1.4 package、檔案與可見性</title><link>https://tarrragon.github.io/blog/go/01-basics/packages/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/packages/</guid><description>&lt;p>Go 用 package 組織程式碼。package 不只是資料夾名稱，而是 API 邊界：哪些名稱能被其他 package 使用，哪些名稱只在內部可見，都由 package 與命名共同決定。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解 &lt;code>package main&lt;/code> 和一般 package 的差異&lt;/li>
&lt;li>看懂同一個 package 如何拆成多個檔案&lt;/li>
&lt;li>用大小寫判斷 exported 與 unexported 名稱&lt;/li>
&lt;li>設計不暴露過多細節的 package API&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察每個-go-檔案都從-package-開始">【觀察】每個 Go 檔案都從 package 開始&lt;/h2>
&lt;p>package 宣告的核心規則是：每個 Go 檔案都必須先宣告自己屬於哪個 package。可執行程式使用 &lt;code>package main&lt;/code>，例如：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&lt;/span> &lt;span class="s">&amp;#34;fmt&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="kd">func&lt;/span> &lt;span class="nf">main&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;hello&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;/code>&lt;/pre>&lt;/div>&lt;p>第一行 &lt;code>package main&lt;/code> 表示這個檔案屬於 &lt;code>main&lt;/code> package。Go 編譯器會尋找 &lt;code>main&lt;/code> package 裡的 &lt;code>main()&lt;/code> 函式，將它編譯成可執行程式。&lt;/p>
&lt;p>一般 package 的核心規則是：它不負責啟動程式，而是提供型別、函式或方法給其他 package 使用。可被其他程式引用的工具 package 通常會使用自己的 package 名稱：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">config&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="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&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">Port&lt;/span> &lt;span class="kt">int&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>這個 package 不會自己啟動程式，而是提供型別、函式或方法給其他 package 使用。&lt;/p>
&lt;h2 id="判讀package-是一組共同編譯的檔案">【判讀】package 是一組共同編譯的檔案&lt;/h2>
&lt;p>package 的編譯單位是一組同 package 檔案。同一個資料夾中的 Go 檔案通常必須宣告同一個 package；這些檔案會被一起編譯，也可以直接互相使用彼此的 unexported 名稱。&lt;/p>
&lt;p>例如：&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">config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── config.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── defaults.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">└── validate.go&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-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">config&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這表示 &lt;code>config.go&lt;/code> 裡的函式可以直接呼叫 &lt;code>validate.go&lt;/code> 裡的小工具函式，即使那個工具函式沒有 exported。&lt;/p>
&lt;h2 id="策略用大小寫控制-api-邊界">【策略】用大小寫控制 API 邊界&lt;/h2>
&lt;p>Go 可見性的核心規則是：大寫開頭 exported，小寫開頭 unexported。Go 沒有 &lt;code>public&lt;/code>、&lt;code>private&lt;/code> 關鍵字，而是用命名大小寫決定可見性：&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&lt;/code>&lt;/td>
 &lt;td>exported&lt;/td>
 &lt;td>其他 package 可使用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>Load&lt;/code>&lt;/td>
 &lt;td>exported&lt;/td>
 &lt;td>其他 package 可呼叫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>defaultPort&lt;/code>&lt;/td>
 &lt;td>unexported&lt;/td>
 &lt;td>只在目前 package 內可用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>validatePath&lt;/code>&lt;/td>
 &lt;td>unexported&lt;/td>
 &lt;td>內部實作細節&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">config&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="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&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">Port&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="nx">defaultPort&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">8080&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="kd">func&lt;/span> &lt;span class="nf">Load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">path&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&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="k">if&lt;/span> &lt;span class="nx">path&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>&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="nx">Config&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">defaultPort&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="kc">nil&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 class="k">return&lt;/span> &lt;span class="nf">readConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">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 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="kd">func&lt;/span> &lt;span class="nf">readConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">path&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&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="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">return&lt;/span> &lt;span class="nx">Config&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">defaultPort&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="kc">nil&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;/code>&lt;/pre>&lt;/div>&lt;p>其他 package 可以使用 &lt;code>config.Config&lt;/code> 和 &lt;code>config.Load&lt;/code>，但不能直接使用 &lt;code>config.defaultPort&lt;/code> 或 &lt;code>config.readConfig&lt;/code>。&lt;/p>
&lt;h2 id="執行把檔案切分成認知單位">【執行】把檔案切分成認知單位&lt;/h2>
&lt;p>檔案切分的核心規則是：依照讀者理解程式的方式分組，而不是機械地一個型別一個檔案。Go 不要求「一個型別一個檔案」。&lt;/p>
&lt;p>例如設定讀取 package 可以這樣切：&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">config/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── config.go # Config 型別與 Load 入口
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── defaults.go # 預設值
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── validate.go # 驗證規則
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── config_test.go # 測試&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這樣切分的好處是：&lt;/p>
&lt;ul>
&lt;li>&lt;code>config.go&lt;/code> 作為 package 入口，讀者先看這裡&lt;/li>
&lt;li>&lt;code>defaults.go&lt;/code> 集中預設值，不和解析流程混在一起&lt;/li>
&lt;li>&lt;code>validate.go&lt;/code> 集中驗證規則，方便測試&lt;/li>
&lt;li>unexported helper 留在 package 內，不污染外部 API&lt;/li>
&lt;/ul>
&lt;h2 id="設計檢查">設計檢查&lt;/h2>
&lt;h3 id="檢查一控制-exported-api">檢查一：控制 exported API&lt;/h3>
&lt;p>如果你把所有型別、函式、常數都用大寫開頭，其他 package 就會開始依賴你的內部細節。未來你想重構時，會發現很多名稱都不能改。&lt;/p></description><content:encoded><![CDATA[<p>Go 用 package 組織程式碼。package 不只是資料夾名稱，而是 API 邊界：哪些名稱能被其他 package 使用，哪些名稱只在內部可見，都由 package 與命名共同決定。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解 <code>package main</code> 和一般 package 的差異</li>
<li>看懂同一個 package 如何拆成多個檔案</li>
<li>用大小寫判斷 exported 與 unexported 名稱</li>
<li>設計不暴露過多細節的 package API</li>
</ol>
<hr>
<h2 id="觀察每個-go-檔案都從-package-開始">【觀察】每個 Go 檔案都從 package 開始</h2>
<p>package 宣告的核心規則是：每個 Go 檔案都必須先宣告自己屬於哪個 package。可執行程式使用 <code>package main</code>，例如：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</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="s">&#34;fmt&#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="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;hello&#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></code></pre></div><p>第一行 <code>package main</code> 表示這個檔案屬於 <code>main</code> package。Go 編譯器會尋找 <code>main</code> package 裡的 <code>main()</code> 函式，將它編譯成可執行程式。</p>
<p>一般 package 的核心規則是：它不負責啟動程式，而是提供型別、函式或方法給其他 package 使用。可被其他程式引用的工具 package 通常會使用自己的 package 名稱：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">config</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="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">Port</span> <span class="kt">int</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這個 package 不會自己啟動程式，而是提供型別、函式或方法給其他 package 使用。</p>
<h2 id="判讀package-是一組共同編譯的檔案">【判讀】package 是一組共同編譯的檔案</h2>
<p>package 的編譯單位是一組同 package 檔案。同一個資料夾中的 Go 檔案通常必須宣告同一個 package；這些檔案會被一起編譯，也可以直接互相使用彼此的 unexported 名稱。</p>
<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">config/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── config.go
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── defaults.go
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── validate.go</span></span></code></pre></div><p>三個檔案都可以是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">config</span></span></span></code></pre></div><p>這表示 <code>config.go</code> 裡的函式可以直接呼叫 <code>validate.go</code> 裡的小工具函式，即使那個工具函式沒有 exported。</p>
<h2 id="策略用大小寫控制-api-邊界">【策略】用大小寫控制 API 邊界</h2>
<p>Go 可見性的核心規則是：大寫開頭 exported，小寫開頭 unexported。Go 沒有 <code>public</code>、<code>private</code> 關鍵字，而是用命名大小寫決定可見性：</p>
<table>
  <thead>
      <tr>
          <th>名稱</th>
          <th>可見性</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Config</code></td>
          <td>exported</td>
          <td>其他 package 可使用</td>
      </tr>
      <tr>
          <td><code>Load</code></td>
          <td>exported</td>
          <td>其他 package 可呼叫</td>
      </tr>
      <tr>
          <td><code>defaultPort</code></td>
          <td>unexported</td>
          <td>只在目前 package 內可用</td>
      </tr>
      <tr>
          <td><code>validatePath</code></td>
          <td>unexported</td>
          <td>內部實作細節</td>
      </tr>
  </tbody>
</table>
<p>範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">package</span> <span class="nx">config</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="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nx">Port</span> <span class="kt">int</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">const</span> <span class="nx">defaultPort</span> <span class="p">=</span> <span class="mi">8080</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="kd">func</span> <span class="nf">Load</span><span class="p">(</span><span class="nx">path</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">Config</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</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="nx">path</span> <span class="o">==</span> <span class="s">&#34;&#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="nx">Config</span><span class="p">{</span><span class="nx">Port</span><span class="p">:</span> <span class="nx">defaultPort</span><span class="p">},</span> <span class="kc">nil</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 class="k">return</span> <span class="nf">readConfig</span><span class="p">(</span><span class="nx">path</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="kd">func</span> <span class="nf">readConfig</span><span class="p">(</span><span class="nx">path</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">Config</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</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="k">return</span> <span class="nx">Config</span><span class="p">{</span><span class="nx">Port</span><span class="p">:</span> <span class="nx">defaultPort</span><span class="p">},</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>其他 package 可以使用 <code>config.Config</code> 和 <code>config.Load</code>，但不能直接使用 <code>config.defaultPort</code> 或 <code>config.readConfig</code>。</p>
<h2 id="執行把檔案切分成認知單位">【執行】把檔案切分成認知單位</h2>
<p>檔案切分的核心規則是：依照讀者理解程式的方式分組，而不是機械地一個型別一個檔案。Go 不要求「一個型別一個檔案」。</p>
<p>例如設定讀取 package 可以這樣切：</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">config/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── config.go       # Config 型別與 Load 入口
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── defaults.go     # 預設值
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── validate.go     # 驗證規則
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── config_test.go  # 測試</span></span></code></pre></div><p>這樣切分的好處是：</p>
<ul>
<li><code>config.go</code> 作為 package 入口，讀者先看這裡</li>
<li><code>defaults.go</code> 集中預設值，不和解析流程混在一起</li>
<li><code>validate.go</code> 集中驗證規則，方便測試</li>
<li>unexported helper 留在 package 內，不污染外部 API</li>
</ul>
<h2 id="設計檢查">設計檢查</h2>
<h3 id="檢查一控制-exported-api">檢查一：控制 exported API</h3>
<p>如果你把所有型別、函式、常數都用大寫開頭，其他 package 就會開始依賴你的內部細節。未來你想重構時，會發現很多名稱都不能改。</p>
<h3 id="檢查二package-名稱表達責任">檢查二：package 名稱表達責任</h3>
<p><code>utils</code>、<code>common</code>、<code>helpers</code> 這類名稱常讓 package 變成雜物間。Go 更偏好用資料或能力命名，例如 <code>config</code>、<code>auth</code>、<code>metrics</code>、<code>parser</code>。</p>
<h3 id="檢查三檔案切分服務閱讀">檢查三：檔案切分服務閱讀</h3>
<p>過度切分會讓讀者一直跳檔案。Go 的檔案可以稍微長一點，只要同一個檔案仍然圍繞同一組概念。</p>
]]></content:encoded></item><item><title>1.4 導入機制與路徑管理</title><link>https://tarrragon.github.io/blog/python/01-basics/imports/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/imports/</guid><description>&lt;p>「ModuleNotFoundError」是 Python 開發者最常遇到的錯誤之一。理解導入機制可以幫助你快速解決這類問題。&lt;/p>
&lt;blockquote>
&lt;p>承接提示：import 問題通常是 script、module、package 與執行位置共同造成的結果，而非單一語法問題。如果還不確定這幾個概念的差異，請先閱讀 &lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="模組搜尋路徑">模組搜尋路徑&lt;/h2>
&lt;p>Python 使用 &lt;code>sys.path&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">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="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">sys&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">3&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&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;/code>&lt;/pre>&lt;/div>&lt;p>典型輸出：&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">/current/script/directory # 腳本所在目錄
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">/usr/local/lib/python3.11 # 標準庫
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">/usr/local/lib/python3.11/site-packages # 第三方套件&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="hook-腳本的導入問題">Hook 腳本的導入問題&lt;/h2>
&lt;h3 id="問題情境">問題情境&lt;/h3>
&lt;p>Hook 腳本位於 &lt;code>.claude/hooks/&lt;/code>，共用模組位於 &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/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── hooks/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ └── branch-verify-hook.py # 需要導入 lib 的模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">└── lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> └── git_utils.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果直接在 Hook 腳本中寫 &lt;code>from git_utils import ...&lt;/code>，會得到 &lt;code>ModuleNotFoundError&lt;/code>。&lt;/p>
&lt;h3 id="解決方案">解決方案&lt;/h3>
&lt;p>在導入前將 lib 目錄加入搜尋路徑：&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="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;Branch Verify 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="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&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"> 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"># 計算 lib 目錄的路徑&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"># __file__ = .claude/hooks/branch-verify-hook.py&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"># parent = .claude/hooks/&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"># parent.parent = .claude/&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"># parent.parent / &amp;#34;lib&amp;#34; = .claude/lib/&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">lib_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&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="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="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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">lib_path&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"># 現在可以導入了&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">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&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="n">read_hook_input&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼用-insert0--而不是-append">為什麼用 &lt;code>insert(0, ...)&lt;/code> 而不是 &lt;code>append(...)&lt;/code>？&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="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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">lib_path&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="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&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">lib_path&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你的模組名稱與標準庫衝突（例如 &lt;code>email.py&lt;/code>），使用 &lt;code>append&lt;/code> 會導致導入標準庫而非你的模組。&lt;/p>
&lt;h2 id="使用-path-物件">使用 Path 物件&lt;/h2>
&lt;h3 id="path-的基本操作">Path 的基本操作&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">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"> 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="n">current_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&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">parent_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">current_file&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"> 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">lib_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parent_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &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="n">lib_path_str&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">lib_path&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"># __file__ 可能是相對路徑&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 可能是 &amp;#34;./hooks/my_hook.py&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">absolute_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resolve&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="n">absolute_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># &amp;#34;/home/user/project/.claude/hooks/my_hook.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="環境變數方式">環境變數方式&lt;/h2>
&lt;p>另一種方法是使用 &lt;code>PYTHONPATH&lt;/code> 環境變數：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&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">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">:/path/to/.claude/lib&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">python .claude/hooks/my_hook.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="專案根目錄的取得">專案根目錄的取得&lt;/h2>
&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">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">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"> 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"> str: 專案根目錄路徑
&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">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;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">9&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;/code>&lt;/pre>&lt;/div>&lt;p>使用範例：&lt;/p></description><content:encoded><![CDATA[<p>「ModuleNotFoundError」是 Python 開發者最常遇到的錯誤之一。理解導入機制可以幫助你快速解決這類問題。</p>
<blockquote>
<p>承接提示：import 問題通常是 script、module、package 與執行位置共同造成的結果，而非單一語法問題。如果還不確定這幾個概念的差異，請先閱讀 <a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案</a>。</p></blockquote>
<h2 id="模組搜尋路徑">模組搜尋路徑</h2>
<p>Python 使用 <code>sys.path</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="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</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="nb">print</span><span class="p">(</span><span class="n">path</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">/current/script/directory     # 腳本所在目錄
</span></span><span class="line"><span class="ln">2</span><span class="cl">/usr/local/lib/python3.11     # 標準庫
</span></span><span class="line"><span class="ln">3</span><span class="cl">/usr/local/lib/python3.11/site-packages  # 第三方套件</span></span></code></pre></div><h2 id="hook-腳本的導入問題">Hook 腳本的導入問題</h2>
<h3 id="問題情境">問題情境</h3>
<p>Hook 腳本位於 <code>.claude/hooks/</code>，共用模組位於 <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/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── hooks/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   └── branch-verify-hook.py    # 需要導入 lib 的模組
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── lib/
</span></span><span class="line"><span class="ln">5</span><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">    └── git_utils.py</span></span></code></pre></div><p>如果直接在 Hook 腳本中寫 <code>from git_utils import ...</code>，會得到 <code>ModuleNotFoundError</code>。</p>
<h3 id="解決方案">解決方案</h3>
<p>在導入前將 lib 目錄加入搜尋路徑：</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;Branch Verify 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="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 計算 lib 目錄的路徑</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># __file__ = .claude/hooks/branch-verify-hook.py</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># parent = .claude/hooks/</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># parent.parent = .claude/</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># parent.parent / &#34;lib&#34; = .claude/lib/</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">lib_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#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="c1"># 插入到搜尋路徑的最前面</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</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"># 現在可以導入了</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h3 id="為什麼用-insert0--而不是-append">為什麼用 <code>insert(0, ...)</code> 而不是 <code>append(...)</code>？</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="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</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">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">lib_path</span><span class="p">))</span></span></span></code></pre></div><p>如果你的模組名稱與標準庫衝突（例如 <code>email.py</code>），使用 <code>append</code> 會導致導入標準庫而非你的模組。</p>
<h2 id="使用-path-物件">使用 Path 物件</h2>
<h3 id="path-的基本操作">Path 的基本操作</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">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"># 取得目前檔案的路徑</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">current_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</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">parent_dir</span> <span class="o">=</span> <span class="n">current_file</span><span class="o">.</span><span class="n">parent</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">lib_path</span> <span class="o">=</span> <span class="n">parent_dir</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span>  <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="n">lib_path_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</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"># __file__ 可能是相對路徑</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>  <span class="c1"># 可能是 &#34;./hooks/my_hook.py&#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">absolute_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</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">6</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">)</span>  <span class="c1"># &#34;/home/user/project/.claude/hooks/my_hook.py&#34;</span></span></span></code></pre></div><h2 id="環境變數方式">環境變數方式</h2>
<p>另一種方法是使用 <code>PYTHONPATH</code> 環境變數：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在 shell 中設定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">export</span> <span class="nv">PYTHONPATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PYTHONPATH</span><span class="si">}</span><span class="s2">:/path/to/.claude/lib&#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">python .claude/hooks/my_hook.py</span></span></code></pre></div><h2 id="專案根目錄的取得">專案根目錄的取得</h2>
<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">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">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">    Returns:
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">        str: 專案根目錄路徑
</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">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;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">9</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></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="n">root</span> <span class="o">=</span> <span class="n">get_project_root</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">config_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="s2">&#34;.claude&#34;</span><span class="p">,</span> <span class="s2">&#34;config.json&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="常見錯誤與解決">常見錯誤與解決</h2>
<h3 id="錯誤-1-modulenotfounderror">錯誤 1: ModuleNotFoundError</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">ModuleNotFoundError: No module named &#39;git_utils&#39;</span></span></code></pre></div><p><strong>解決方案</strong>：確認 <code>sys.path.insert()</code> 在導入語句之前。</p>
<h3 id="錯誤-2-相對導入錯誤">錯誤 2: 相對導入錯誤</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">ImportError: attempted relative import with no known parent package</span></span></code></pre></div><p><strong>原因</strong>：在腳本中使用相對導入。</p>
<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="c1"># 錯誤：在腳本中使用相對導入</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">.lib</span> <span class="kn">import</span> <span class="n">git_utils</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="kn">from</span> <span class="nn">lib</span> <span class="kn">import</span> <span class="n">git_utils</span></span></span></code></pre></div><h3 id="錯誤-3-循環導入">錯誤 3: 循環導入</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">ImportError: cannot import name &#39;xxx&#39; from partially initialized module</span></span></code></pre></div><p><strong>解決方案</strong>：重構程式碼，避免模組間互相依賴。</p>
<h2 id="導入風格指南">導入風格指南</h2>
<h3 id="導入順序pep-8">導入順序（PEP 8）</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"># 1. 標準庫</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 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="c1"># 2. 第三方套件</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</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. 本地模組</span>
</span></span><span class="line"><span class="ln">11</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></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h3 id="避免-import-">避免 <code>import *</code></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="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="o">*</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="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">6</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">get_project_root</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></span><span class="line"><span class="ln">9</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="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">2</span><span class="cl">    <span class="n">run_git_command</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">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">is_allowed_branch</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="實際範例完整的-hook-腳本開頭">實際範例：完整的 Hook 腳本開頭</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">Branch Verify 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="kn">import</span> <span class="nn">os</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">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></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"># 將 lib 目錄加入搜尋路徑</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</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"># ===== 本地模組導入 =====</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">is_protected_branch</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span><span class="p">,</span> <span class="n">create_pretooluse_output</span>
</span></span><span class="line"><span class="ln">20</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">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">logger</span> <span class="o">=</span> <span class="n">setup_hook_logging</span><span class="p">(</span><span class="s2">&#34;branch-verify&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼不把 lib 目錄加入 <code>PYTHONPATH</code> 環境變數，而要在每個腳本中設定 <code>sys.path</code>？</li>
<li><code>Path(__file__).resolve()</code> 和 <code>Path(__file__)</code> 有什麼區別？</li>
<li>如何驗證一個路徑是否已經在 <code>sys.path</code> 中？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>寫一個函式，列出 <code>sys.path</code> 中所有存在的目錄</li>
<li>建立一個簡單的模組，然後在另一個檔案中使用兩種不同的方式導入它</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">模組與套件組織</a></em>
<em>下一模組：<a href="/blog/python/02-type-system/" data-link-title="模組二：型別系統" data-link-desc="現代 Python 的型別提示與資料結構">型別系統</a></em></p>
]]></content:encoded></item><item><title>1.5 從單檔到多檔案</title><link>https://tarrragon.github.io/blog/go/01-basics/growing-files-packages/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/growing-files-packages/</guid><description>&lt;p>Go 程式變大的第一個拆分單位通常是檔案，不是架構。學習與開發常從一個 &lt;code>main.go&lt;/code> 開始，等到入口程式太長，再把相關函式拆到同一個 package 的其他檔案；只有當某組概念需要形成獨立 API 邊界時，才搬到新的資料夾成為新的 package。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>判斷何時保留單一 &lt;code>main.go&lt;/code>&lt;/li>
&lt;li>理解同 package 多檔案如何互相呼叫&lt;/li>
&lt;li>分辨「拆檔案」和「拆 package」的差異&lt;/li>
&lt;li>看懂跨 package 呼叫、exported 名稱與 import path 的關係&lt;/li>
&lt;li>避免過早把小程式拆成複雜架構&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察單檔是合理起點">【觀察】單檔是合理起點&lt;/h2>
&lt;p>單一 &lt;code>main.go&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">└── main.go&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一個最小 HTTP 服務可以先長這樣：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kn">import&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="s">&amp;#34;fmt&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="s">&amp;#34;net/http&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="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="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/health&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">healthHandler&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:8080&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="nb">panic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">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="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>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">healthHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fprintln&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;ok&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;/code>&lt;/pre>&lt;/div>&lt;p>這個階段不需要急著建立 &lt;code>handler&lt;/code>、&lt;code>service&lt;/code> 或 &lt;code>domain&lt;/code> 資料夾。讀者一眼能看懂程式如何啟動，比形式上的分層更重要。&lt;/p>
&lt;h2 id="判讀maingo-膨脹時先拆同-package-多檔案">【判讀】main.go 膨脹時，先拆同 package 多檔案&lt;/h2>
&lt;p>同 package 多檔案的核心規則是：同一個資料夾、同一個 package 名稱的 Go 檔案會被一起編譯，彼此可以直接呼叫，不需要 import。&lt;/p>
&lt;p>當 &lt;code>main.go&lt;/code> 開始同時包含設定、handler、資料型別與啟動流程，可以先拆成這樣：&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── config.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── server.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">└── message.go&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-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>因此 &lt;code>main.go&lt;/code> 可以直接呼叫 &lt;code>loadConfig()&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&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">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">loadConfig&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">server&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">newServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cfg&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">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">server&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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">panic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&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 class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>config.go&lt;/code> 可以提供這個函式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kd">type&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="kd">struct&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">Port&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">loadConfig&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="nx">config&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="nx">config&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;:8080&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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這是同 package 內的檔案切分。&lt;code>loadConfig&lt;/code> 即使用小寫開頭，&lt;code>main.go&lt;/code> 也可以呼叫，因為它們都屬於 &lt;code>package main&lt;/code>。&lt;/p>
&lt;h2 id="策略先拆檔案再拆-package">【策略】先拆檔案，再拆 package&lt;/h2>
&lt;p>拆分的核心判斷是：檔案用來降低閱讀負擔，package 用來建立 API 邊界。這兩者成本不同，不應混在一起。&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>同 package 多檔案&lt;/td>
 &lt;td>檔案太長、概念需要分段&lt;/td>
 &lt;td>直接呼叫&lt;/td>
 &lt;td>成本低，沒有新 API 邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新 package&lt;/td>
 &lt;td>概念可獨立、需要被其他 package 使用&lt;/td>
 &lt;td>import 後呼叫 exported 名稱&lt;/td>
 &lt;td>成本較高，需要設計 API&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>例如 &lt;code>message.go&lt;/code> 只是放一些內部型別時，可以留在 &lt;code>package main&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&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="kd">type&lt;/span> &lt;span class="nx">message&lt;/span> &lt;span class="kd">struct&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">Title&lt;/span> &lt;span class="kt">string&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">Body&lt;/span> &lt;span class="kt">string&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;/code>&lt;/pre>&lt;/div>&lt;p>如果 notification 概念開始有自己的建構規則、驗證規則與測試需求，就可以搬成獨立 package：&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">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── go.mod
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">└── notification/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ├── notification.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> └── validate.go&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此時 &lt;code>notification&lt;/code> package 要明確決定哪些名稱是對外 API。&lt;/p>
&lt;h2 id="執行跨-package-呼叫需要-import-與-exported-名稱">【執行】跨 package 呼叫需要 import 與 exported 名稱&lt;/h2>
&lt;p>跨 package 呼叫的核心規則是：其他 package 只能使用大寫開頭的 exported 名稱，並且必須透過 import path 引入。&lt;/p>
&lt;p>假設 module path 是：&lt;/p></description><content:encoded><![CDATA[<p>Go 程式變大的第一個拆分單位通常是檔案，不是架構。學習與開發常從一個 <code>main.go</code> 開始，等到入口程式太長，再把相關函式拆到同一個 package 的其他檔案；只有當某組概念需要形成獨立 API 邊界時，才搬到新的資料夾成為新的 package。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>判斷何時保留單一 <code>main.go</code></li>
<li>理解同 package 多檔案如何互相呼叫</li>
<li>分辨「拆檔案」和「拆 package」的差異</li>
<li>看懂跨 package 呼叫、exported 名稱與 import path 的關係</li>
<li>避免過早把小程式拆成複雜架構</li>
</ol>
<hr>
<h2 id="觀察單檔是合理起點">【觀察】單檔是合理起點</h2>
<p>單一 <code>main.go</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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── main.go</span></span></code></pre></div><p>一個最小 HTTP 服務可以先長這樣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</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="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s">&#34;net/http&#34;</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="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/health&#34;</span><span class="p">,</span> <span class="nx">healthHandler</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">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:8080&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</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 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="kd">func</span> <span class="nf">healthHandler</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Fprintln</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;ok&#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></code></pre></div><p>這個階段不需要急著建立 <code>handler</code>、<code>service</code> 或 <code>domain</code> 資料夾。讀者一眼能看懂程式如何啟動，比形式上的分層更重要。</p>
<h2 id="判讀maingo-膨脹時先拆同-package-多檔案">【判讀】main.go 膨脹時，先拆同 package 多檔案</h2>
<p>同 package 多檔案的核心規則是：同一個資料夾、同一個 package 名稱的 Go 檔案會被一起編譯，彼此可以直接呼叫，不需要 import。</p>
<p>當 <code>main.go</code> 開始同時包含設定、handler、資料型別與啟動流程，可以先拆成這樣：</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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── main.go
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── config.go
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── server.go
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── message.go</span></span></code></pre></div><p>每個檔案仍然使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</span></span></span></code></pre></div><p>因此 <code>main.go</code> 可以直接呼叫 <code>loadConfig()</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">cfg</span> <span class="o">:=</span> <span class="nf">loadConfig</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">server</span> <span class="o">:=</span> <span class="nf">newServer</span><span class="p">(</span><span class="nx">cfg</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">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">server</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</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 class="p">}</span></span></span></code></pre></div><p><code>config.go</code> 可以提供這個函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</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="kd">type</span> <span class="nx">config</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">Port</span> <span class="kt">string</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="kd">func</span> <span class="nf">loadConfig</span><span class="p">()</span> <span class="nx">config</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="nx">config</span><span class="p">{</span><span class="nx">Port</span><span class="p">:</span> <span class="s">&#34;:8080&#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></code></pre></div><p>這是同 package 內的檔案切分。<code>loadConfig</code> 即使用小寫開頭，<code>main.go</code> 也可以呼叫，因為它們都屬於 <code>package main</code>。</p>
<h2 id="策略先拆檔案再拆-package">【策略】先拆檔案，再拆 package</h2>
<p>拆分的核心判斷是：檔案用來降低閱讀負擔，package 用來建立 API 邊界。這兩者成本不同，不應混在一起。</p>
<table>
  <thead>
      <tr>
          <th>拆分方式</th>
          <th>使用時機</th>
          <th>呼叫方式</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同 package 多檔案</td>
          <td>檔案太長、概念需要分段</td>
          <td>直接呼叫</td>
          <td>成本低，沒有新 API 邊界</td>
      </tr>
      <tr>
          <td>新 package</td>
          <td>概念可獨立、需要被其他 package 使用</td>
          <td>import 後呼叫 exported 名稱</td>
          <td>成本較高，需要設計 API</td>
      </tr>
  </tbody>
</table>
<p>例如 <code>message.go</code> 只是放一些內部型別時，可以留在 <code>package main</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</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="kd">type</span> <span class="nx">message</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">Title</span> <span class="kt">string</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">Body</span>  <span class="kt">string</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>如果 notification 概念開始有自己的建構規則、驗證規則與測試需求，就可以搬成獨立 package：</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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── main.go
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── notification/
</span></span><span class="line"><span class="ln">5</span><span class="cl">    ├── notification.go
</span></span><span class="line"><span class="ln">6</span><span class="cl">    └── validate.go</span></span></code></pre></div><p>此時 <code>notification</code> package 要明確決定哪些名稱是對外 API。</p>
<h2 id="執行跨-package-呼叫需要-import-與-exported-名稱">【執行】跨 package 呼叫需要 import 與 exported 名稱</h2>
<p>跨 package 呼叫的核心規則是：其他 package 只能使用大寫開頭的 exported 名稱，並且必須透過 import path 引入。</p>
<p>假設 module path 是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">module</span> <span class="nx">example</span><span class="p">.</span><span class="nx">com</span><span class="o">/</span><span class="nx">notify</span></span></span></code></pre></div><p><code>notification/notification.go</code> 可以這樣寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">package</span> <span class="nx">notification</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="s">&#34;strings&#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="kd">type</span> <span class="nx">Notification</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">Title</span> <span class="kt">string</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">Body</span>  <span class="kt">string</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="kd">func</span> <span class="nf">New</span><span class="p">(</span><span class="nx">title</span><span class="p">,</span> <span class="nx">body</span> <span class="kt">string</span><span class="p">)</span> <span class="nx">Notification</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="nx">Notification</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nx">Title</span><span class="p">:</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSpace</span><span class="p">(</span><span class="nx">title</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nx">Body</span><span class="p">:</span>  <span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSpace</span><span class="p">(</span><span class="nx">body</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="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="kd">func</span> <span class="nf">isEmpty</span><span class="p">(</span><span class="nx">n</span> <span class="nx">Notification</span><span class="p">)</span> <span class="kt">bool</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="nx">n</span><span class="p">.</span><span class="nx">Title</span> <span class="o">==</span> <span class="s">&#34;&#34;</span> <span class="o">&amp;&amp;</span> <span class="nx">n</span><span class="p">.</span><span class="nx">Body</span> <span class="o">==</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>main.go</code> 要使用這個 package，必須 import：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">package</span> <span class="nx">main</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="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s">&#34;fmt&#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="s">&#34;example.com/notify/notification&#34;</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="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">n</span> <span class="o">:=</span> <span class="nx">notification</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;Deploy finished&#34;</span><span class="p">,</span> <span class="s">&#34;Version 1.2.0 is live&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">n</span><span class="p">.</span><span class="nx">Title</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>notification.New</code> 和 <code>notification.Notification</code> 可以被外部使用，因為它們是大寫開頭。<code>isEmpty</code> 不能被 <code>main.go</code> 呼叫，因為它是 package 內部實作細節。</p>
<h2 id="判讀import-cycle-是依賴方向錯了">【判讀】import cycle 是依賴方向錯了</h2>
<p>import cycle 的核心意義是兩個 package 互相依賴，Go 會直接拒絕編譯。Go 工具鏈透過這個限制強迫你把依賴方向想清楚。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── main.go
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── handler/
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── handler.go
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── notification/
</span></span><span class="line"><span class="ln">6</span><span class="cl">    └── notification.go</span></span></code></pre></div><p>如果 <code>handler</code> import <code>notification</code>，同時 <code>notification</code> 又 import <code>handler</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">handler -&gt; notification -&gt; handler</span></span></code></pre></div><p>修正的核心做法是讓低層概念不要依賴高層協定。<code>notification</code> 應該描述通知資料與規則，不應知道 HTTP handler；handler 可以把 HTTP request 轉成 notification command 或 value。</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">handler -&gt; notification</span></span></code></pre></div><p>這個方向比「互相知道」更容易測試，也更容易重構。</p>
<h2 id="常見拆分路線">常見拆分路線</h2>
<p>Go 服務常見的成長路線是漸進式的。每一步都應該解決當下的閱讀、測試或依賴問題，而不是為了看起來正式。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── main.go</span></span></code></pre></div><p>適合小工具、實驗程式、剛開始的服務雛形。</p>
<h3 id="階段二同-package-多檔案">階段二：同 package 多檔案</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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── main.go
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── config.go
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── server.go
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── notification.go</span></span></code></pre></div><p>適合 <code>main.go</code> 開始太長，但概念還沒有明確 API 邊界的階段。</p>
<h3 id="階段三多-package">階段三：多 package</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">notify/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── main.go
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── config/
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   └── config.go
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">├── notification/
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│   ├── notification.go
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   └── validate.go
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">└── transport/
</span></span><span class="line"><span class="ln">10</span><span class="cl">    └── http.go</span></span></code></pre></div><p>適合設定、通知規則、HTTP transport 已經能清楚分成不同責任的階段。</p>
<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">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── go.mod
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── cmd/
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── notify-server/
</span></span><span class="line"><span class="ln">5</span><span class="cl">│       └── main.go
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── internal/
</span></span><span class="line"><span class="ln">7</span><span class="cl">    ├── notification/
</span></span><span class="line"><span class="ln">8</span><span class="cl">    ├── transport/
</span></span><span class="line"><span class="ln">9</span><span class="cl">    └── storage/</span></span></code></pre></div><p>適合服務已經有明確部署入口、內部 package 不想被外部 module 引用的階段。這是程式長大後的選擇。</p>
<h2 id="設計檢查">設計檢查</h2>
<h3 id="檢查一檔案切分跟著-go-package-模型">檢查一：檔案切分跟著 Go package 模型</h3>
<p>Go 的檔案不是 class。把每個 struct 都拆成一個檔案，通常只會增加跳轉成本，不會讓設計更清楚。</p>
<h3 id="檢查二資料夾跟著邊界成長">檢查二：資料夾跟著邊界成長</h3>
<p>程式還小時就建立 <code>domain</code>、<code>application</code>、<code>infrastructure</code>，會讓讀者先學資料夾，再學行為本身。Go 更適合先讓程式跑起來，再根據壓力拆邊界。</p>
<h3 id="檢查三exported-名稱代表公開承諾">檢查三：exported 名稱代表公開承諾</h3>
<p>exported 名稱就是 package 對外承諾。還不確定會被外部使用的型別與函式，先保持 unexported，等 API 真的穩定再開放。</p>
]]></content:encoded></item><item><title>1.6 函式、方法與 receiver</title><link>https://tarrragon.github.io/blog/go/01-basics/functions-methods/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/functions-methods/</guid><description>&lt;p>Go 沒有 class，但有函式、struct 與方法。方法只是帶有 receiver 的函式；receiver 讓函式和某個型別形成關聯，進而表達「這個行為屬於這個資料」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>區分普通函式與方法&lt;/li>
&lt;li>理解 receiver 的語法與語義&lt;/li>
&lt;li>判斷何時使用 pointer receiver&lt;/li>
&lt;li>用建構函式集中初始化規則&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察普通函式不屬於特定值">【觀察】普通函式不屬於特定值&lt;/h2>
&lt;p>普通函式的核心規則是：它不綁定特定型別，只接收參數並回傳結果。以下函式只負責把名稱正規化：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NormalizeName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">string&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">return&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">TrimSpace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ToLower&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">name&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="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-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">name&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NormalizeName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34; Alice &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這種函式適合描述純粹轉換：輸入什麼，輸出什麼，不需要修改某個物件的內部狀態。&lt;/p>
&lt;h2 id="判讀方法是帶-receiver-的函式">【判讀】方法是帶 receiver 的函式&lt;/h2>
&lt;p>方法的核心規則是：函式若需要以某個型別作為操作對象，就用 receiver 綁到該型別。行為和某個型別密切相關時，可以寫成方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Counter&lt;/span> &lt;span class="kd">struct&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">value&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Inc&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="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="o">++&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Value&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">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="k">return&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&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>&lt;code>(c *Counter)&lt;/code> 和 &lt;code>(c Counter)&lt;/code> 就是 receiver。它放在 &lt;code>func&lt;/code> 和方法名稱之間，表示這個函式是 &lt;code>Counter&lt;/code> 的方法。receiver 只表示「這個函式以這個型別作為操作對象」，不代表繼承關係 — Go 沒有類別繼承，方法只是綁定到型別的函式。&lt;/p>
&lt;p>呼叫方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">c&lt;/span> &lt;span class="nx">Counter&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">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Inc&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">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Value&lt;/span>&lt;span class="p">())&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Go 會讓方法呼叫看起來像物件操作，但本質仍然是函式呼叫。&lt;/p>
&lt;h2 id="策略用是否修改狀態選擇-receiver">【策略】用是否修改狀態選擇 receiver&lt;/h2>
&lt;p>receiver 選擇的核心規則是：要修改原值用 pointer receiver，不修改且型別小可以用 value receiver。receiver 有兩種常見形式：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>receiver&lt;/th>
 &lt;th>適用情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>value receiver&lt;/td>
 &lt;td>不修改原值，型別小，複製成本低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>pointer receiver&lt;/td>
 &lt;td>需要修改原值，或型別較大，避免複製&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>例如 &lt;code>Value()&lt;/code> 不修改 &lt;code>Counter&lt;/code>，可以用 value receiver：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Value&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">int&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">return&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&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>Inc()&lt;/code> 需要修改原本的 counter，所以使用 pointer receiver：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Counter&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Inc&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">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">value&lt;/span>&lt;span class="o">++&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你不確定，先問一個問題：這個方法是否要改變 receiver 的狀態？答案是 yes，通常就用 pointer receiver。&lt;/p>
&lt;h2 id="執行用建構函式集中初始化">【執行】用建構函式集中初始化&lt;/h2>
&lt;p>建構函式的核心用途是集中初始化規則。Go 沒有 constructor 關鍵字，但慣例會用 &lt;code>NewTypeName&lt;/code> 建立需要初始化的型別：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Cache&lt;/span> &lt;span class="kd">struct&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">items&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&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="kd">func&lt;/span> &lt;span class="nf">NewCache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Cache&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="o">&amp;amp;&lt;/span>&lt;span class="nx">Cache&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">items&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">string&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 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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Cache&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">value&lt;/span> &lt;span class="kt">string&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 class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">items&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">value&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Cache&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">key&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&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">16&lt;/span>&lt;span class="cl"> &lt;span class="nx">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">items&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&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="nx">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&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;/code>&lt;/pre>&lt;/div>&lt;p>使用方式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">cache&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewCache&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">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;theme&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;dark&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">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;theme&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這裡 &lt;code>NewCache()&lt;/code> 的價值是保證 &lt;code>items&lt;/code> 一定被初始化。呼叫者不需要知道 &lt;code>Cache&lt;/code> 內部有 map，也不需要記得手動 &lt;code>make&lt;/code>。&lt;/p>
&lt;h2 id="函式還是方法">函式還是方法？&lt;/h2>
&lt;p>函式與方法的選擇規則是：純轉換用函式，依賴型別狀態用方法，需要初始化保證用 &lt;code>NewTypeName&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;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要保證初始化規則？&lt;/td>
 &lt;td>&lt;code>NewTypeName&lt;/code> 建構函式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要符合 interface？&lt;/td>
 &lt;td>方法&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">ParsePort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">raw&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&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">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="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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Server&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Start&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">error&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">// 啟動 Server，適合方法&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;/code>&lt;/pre>&lt;/div></description><content:encoded><![CDATA[<p>Go 沒有 class，但有函式、struct 與方法。方法只是帶有 receiver 的函式；receiver 讓函式和某個型別形成關聯，進而表達「這個行為屬於這個資料」。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>區分普通函式與方法</li>
<li>理解 receiver 的語法與語義</li>
<li>判斷何時使用 pointer receiver</li>
<li>用建構函式集中初始化規則</li>
</ol>
<hr>
<h2 id="觀察普通函式不屬於特定值">【觀察】普通函式不屬於特定值</h2>
<p>普通函式的核心規則是：它不綁定特定型別，只接收參數並回傳結果。以下函式只負責把名稱正規化：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">NormalizeName</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">return</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSpace</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToLower</span><span class="p">(</span><span class="nx">name</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">3</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-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">name</span> <span class="o">:=</span> <span class="nf">NormalizeName</span><span class="p">(</span><span class="s">&#34; Alice &#34;</span><span class="p">)</span></span></span></code></pre></div><p>這種函式適合描述純粹轉換：輸入什麼，輸出什麼，不需要修改某個物件的內部狀態。</p>
<h2 id="判讀方法是帶-receiver-的函式">【判讀】方法是帶 receiver 的函式</h2>
<p>方法的核心規則是：函式若需要以某個型別作為操作對象，就用 receiver 綁到該型別。行為和某個型別密切相關時，可以寫成方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">Counter</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">value</span> <span class="kt">int</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Counter</span><span class="p">)</span> <span class="nf">Inc</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">c</span><span class="p">.</span><span class="nx">value</span><span class="o">++</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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="nx">Counter</span><span class="p">)</span> <span class="nf">Value</span><span class="p">()</span> <span class="kt">int</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="nx">c</span><span class="p">.</span><span class="nx">value</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>(c *Counter)</code> 和 <code>(c Counter)</code> 就是 receiver。它放在 <code>func</code> 和方法名稱之間，表示這個函式是 <code>Counter</code> 的方法。receiver 只表示「這個函式以這個型別作為操作對象」，不代表繼承關係 — Go 沒有類別繼承，方法只是綁定到型別的函式。</p>
<p>呼叫方式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">c</span> <span class="nx">Counter</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">c</span><span class="p">.</span><span class="nf">Inc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nf">Value</span><span class="p">())</span></span></span></code></pre></div><p>Go 會讓方法呼叫看起來像物件操作，但本質仍然是函式呼叫。</p>
<h2 id="策略用是否修改狀態選擇-receiver">【策略】用是否修改狀態選擇 receiver</h2>
<p>receiver 選擇的核心規則是：要修改原值用 pointer receiver，不修改且型別小可以用 value receiver。receiver 有兩種常見形式：</p>
<table>
  <thead>
      <tr>
          <th>receiver</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>value receiver</td>
          <td>不修改原值，型別小，複製成本低</td>
      </tr>
      <tr>
          <td>pointer receiver</td>
          <td>需要修改原值，或型別較大，避免複製</td>
      </tr>
  </tbody>
</table>
<p>例如 <code>Value()</code> 不修改 <code>Counter</code>，可以用 value receiver：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="nx">Counter</span><span class="p">)</span> <span class="nf">Value</span><span class="p">()</span> <span class="kt">int</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">value</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>Inc()</code> 需要修改原本的 counter，所以使用 pointer receiver：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Counter</span><span class="p">)</span> <span class="nf">Inc</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">c</span><span class="p">.</span><span class="nx">value</span><span class="o">++</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>如果你不確定，先問一個問題：這個方法是否要改變 receiver 的狀態？答案是 yes，通常就用 pointer receiver。</p>
<h2 id="執行用建構函式集中初始化">【執行】用建構函式集中初始化</h2>
<p>建構函式的核心用途是集中初始化規則。Go 沒有 constructor 關鍵字，但慣例會用 <code>NewTypeName</code> 建立需要初始化的型別：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">Cache</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">items</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><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="kd">func</span> <span class="nf">NewCache</span><span class="p">()</span> <span class="o">*</span><span class="nx">Cache</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="o">&amp;</span><span class="nx">Cache</span><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nx">items</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</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 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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Cache</span><span class="p">)</span> <span class="nf">Set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span> <span class="kt">string</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">c</span><span class="p">.</span><span class="nx">items</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="p">=</span> <span class="nx">value</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="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Cache</span><span class="p">)</span> <span class="nf">Get</span><span class="p">(</span><span class="nx">key</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">string</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nx">value</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nx">items</span><span class="p">[</span><span class="nx">key</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="nx">value</span><span class="p">,</span> <span class="nx">ok</span>
</span></span><span class="line"><span class="ln">18</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-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">cache</span> <span class="o">:=</span> <span class="nf">NewCache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">cache</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;theme&#34;</span><span class="p">,</span> <span class="s">&#34;dark&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">value</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">cache</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;theme&#34;</span><span class="p">)</span></span></span></code></pre></div><p>這裡 <code>NewCache()</code> 的價值是保證 <code>items</code> 一定被初始化。呼叫者不需要知道 <code>Cache</code> 內部有 map，也不需要記得手動 <code>make</code>。</p>
<h2 id="函式還是方法">函式還是方法？</h2>
<p>函式與方法的選擇規則是：純轉換用函式，依賴型別狀態用方法，需要初始化保證用 <code>NewTypeName</code>。可以用這張表判斷：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>選擇</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只是轉換資料？</td>
          <td>普通函式</td>
      </tr>
      <tr>
          <td>行為依賴某個型別的狀態？</td>
          <td>方法</td>
      </tr>
      <tr>
          <td>需要保證初始化規則？</td>
          <td><code>NewTypeName</code> 建構函式</td>
      </tr>
      <tr>
          <td>需要符合 interface？</td>
          <td>方法</td>
      </tr>
  </tbody>
</table>
<p>範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">ParsePort</span><span class="p">(</span><span class="nx">raw</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</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="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="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">Server</span><span class="p">)</span> <span class="nf">Start</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1">// 啟動 Server，適合方法</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div>]]></content:encoded></item><item><title>模組六：實戰指南</title><link>https://tarrragon.github.io/blog/python/06-practical/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/06-practical/</guid><description>&lt;p>本模組將前五個模組的知識整合起來，透過三個實戰案例，教你如何在 Hook 系統中進行實際開發。&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/06-practical/new-hook/" data-link-title="6.1 如何新增一個 Hook" data-link-desc="完整的 Hook 開發流程">6.1&lt;/a>&lt;/td>
 &lt;td>如何新增一個 Hook&lt;/td>
 &lt;td>完整開發流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/06-practical/extend-lib/" data-link-title="6.2 如何擴展共用模組" data-link-desc="為 Hook 系統添加新功能">6.2&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/python/06-practical/new-parser/" data-link-title="6.3 如何新增語言解析器" data-link-desc="繼承 ABC 實作新解析器">6.3&lt;/a>&lt;/td>
 &lt;td>如何新增語言解析器&lt;/td>
 &lt;td>繼承 ABC 實作&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="前置要求">前置要求&lt;/h2>
&lt;p>建議先完成以下模組：&lt;/p>
&lt;ul>
&lt;li>模組一：理解 Python 模組系統&lt;/li>
&lt;li>模組二：掌握型別提示&lt;/li>
&lt;li>模組四：了解抽象基類（對於 6.3）&lt;/li>
&lt;li>模組五：掌握測試技巧&lt;/li>
&lt;/ul>
&lt;h2 id="實戰流程預覽">實戰流程預覽&lt;/h2>
&lt;h3 id="新增-hook-的完整流程">新增 Hook 的完整流程&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. 確定 Hook 類型和觸發時機
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2. 建立腳本檔案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">3. 實作核心邏輯
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">4. 設定 Claude Code 配置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">5. 撰寫測試
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">6. 更新文件&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. 分析需求和現有架構
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2. 設計新功能介面
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">3. 實作並保持向後相容
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">4. 更新所有使用者
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">5. 撰寫測試&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. 繼承 BaseParser
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2. 實作 parse 方法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">3. 註冊到 ParserFactory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">4. 撰寫測試&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>每章節約 30-45 分鐘，全模組約 90-135 分鐘&lt;/p></description><content:encoded><![CDATA[<p>本模組將前五個模組的知識整合起來，透過三個實戰案例，教你如何在 Hook 系統中進行實際開發。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python/06-practical/new-hook/" data-link-title="6.1 如何新增一個 Hook" data-link-desc="完整的 Hook 開發流程">6.1</a></td>
          <td>如何新增一個 Hook</td>
          <td>完整開發流程</td>
      </tr>
      <tr>
          <td><a href="/blog/python/06-practical/extend-lib/" data-link-title="6.2 如何擴展共用模組" data-link-desc="為 Hook 系統添加新功能">6.2</a></td>
          <td>如何擴展共用模組</td>
          <td>模組設計與整合</td>
      </tr>
      <tr>
          <td><a href="/blog/python/06-practical/new-parser/" data-link-title="6.3 如何新增語言解析器" data-link-desc="繼承 ABC 實作新解析器">6.3</a></td>
          <td>如何新增語言解析器</td>
          <td>繼承 ABC 實作</td>
      </tr>
  </tbody>
</table>
<h2 id="前置要求">前置要求</h2>
<p>建議先完成以下模組：</p>
<ul>
<li>模組一：理解 Python 模組系統</li>
<li>模組二：掌握型別提示</li>
<li>模組四：了解抽象基類（對於 6.3）</li>
<li>模組五：掌握測試技巧</li>
</ul>
<h2 id="實戰流程預覽">實戰流程預覽</h2>
<h3 id="新增-hook-的完整流程">新增 Hook 的完整流程</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. 確定 Hook 類型和觸發時機
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 建立腳本檔案
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 實作核心邏輯
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 設定 Claude Code 配置
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 撰寫測試
</span></span><span class="line"><span class="ln">6</span><span class="cl">6. 更新文件</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. 分析需求和現有架構
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 設計新功能介面
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 實作並保持向後相容
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 更新所有使用者
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 撰寫測試</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. 繼承 BaseParser
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 實作 parse 方法
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 註冊到 ParserFactory
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 撰寫測試</span></span></code></pre></div><h2 id="學習時間">學習時間</h2>
<p>每章節約 30-45 分鐘，全模組約 90-135 分鐘</p>
]]></content:encoded></item><item><title>1.7 從入口程式看應用啟動流程</title><link>https://tarrragon.github.io/blog/go/01-basics/main-flow/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/01-basics/main-flow/</guid><description>&lt;p>入口程式是 Go 應用的系統地圖。它不一定包含最多細節，但應該讓你知道 process 如何初始化、哪些 goroutine 會啟動、HTTP endpoint 如何註冊，以及程式如何關閉。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>用啟動流程理解 Go 應用結構&lt;/li>
&lt;li>看懂 channel 與元件之間的資料流&lt;/li>
&lt;li>理解 &lt;code>context.WithCancel&lt;/code> 在關閉流程中的角色&lt;/li>
&lt;li>判斷新增功能應該接在哪個生命週期位置&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察入口流程分成五段">【觀察】入口流程分成五段&lt;/h2>
&lt;p>入口程式的核心責任是揭露應用如何啟動，而不是承載所有實作細節。一個稍具規模的 &lt;code>main()&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>Runtime 與日誌設定&lt;/td>
 &lt;td>設定記憶體限制、初始化 &lt;code>slog&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>環境設定&lt;/td>
 &lt;td>讀取設定檔、環境變數與 port&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>元件組裝&lt;/td>
 &lt;td>建立 repository、worker、service 或 server&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>背景工作&lt;/td>
 &lt;td>啟動 worker、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer&lt;/a> 或定時任務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>對外介面&lt;/td>
 &lt;td>註冊 CLI command、HTTP endpoint 或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> route&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這種切法讓入口同時保留完整脈絡，又不把所有實作細節塞進 &lt;code>main()&lt;/code>。&lt;/p>
&lt;h2 id="判讀main-的價值是揭露依賴關係">【判讀】&lt;code>main()&lt;/code> 的價值是揭露依賴關係&lt;/h2>
&lt;p>&lt;code>main()&lt;/code> 的核心價值是讓依賴關係可見。Go 專案常把依賴直接組裝在 &lt;code>main()&lt;/code>，好處是維護者能直接看到應用骨架：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">events&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">Event&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">128&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">notifications&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">Notification&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">128&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="nx">repo&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewNotificationRepository&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">worker&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewWorker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">events&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">notifications&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">server&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewHTTPServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">notifications&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式揭露一個重要事實：&lt;code>repo&lt;/code> 負責保存共享狀態，&lt;code>worker&lt;/code> 負責處理背景事件，&lt;code>server&lt;/code> 負責提供 HTTP 入口。資料如何流動，不需要先查框架設定就能看懂。&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>新 HTTP endpoint&lt;/td>
 &lt;td>入口程式註冊 route，實作獨立 handler&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新背景事件來源&lt;/td>
 &lt;td>新增 channel、worker 或 queue consumer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新即時訊息 action&lt;/td>
 &lt;td>message router 或連線管理元件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新狀態欄位&lt;/td>
 &lt;td>repository 更新與 model 擴展&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新診斷能力&lt;/td>
 &lt;td>條件註冊 endpoint 或 &lt;code>slog&lt;/code> 欄位&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個判斷可以避免把功能塞進錯誤元件，造成後續難測與難改。&lt;/p>
&lt;h2 id="執行完整啟動路徑">【執行】完整啟動路徑&lt;/h2>
&lt;p>啟動路徑的核心用途是提供除錯地圖。一個有背景工作與 HTTP 介面的 Go 應用，啟動後的主要路徑可能如下：&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">main()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> ├─ setup logger / runtime
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> ├─ create channels
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> ├─ create repository / worker / server
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> ├─ context.WithCancel()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> ├─ go worker.Run(ctx)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> ├─ go startPeriodicSync(ctx)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> ├─ go server.Run(ctx)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> ├─ register /health
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> ├─ register /ws
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> ├─ register /events
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> ├─ go waitForShutdown(cancel, ...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> └─ http.Server.ListenAndServe()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這份路徑也是除錯清單。當應用沒有產生預期輸出時，可以依序確認：輸入來源是否產生資料、worker 是否處理資料、對外介面是否有 client 或呼叫者、狀態資料是否被正確更新。&lt;/p></description><content:encoded><![CDATA[<p>入口程式是 Go 應用的系統地圖。它不一定包含最多細節，但應該讓你知道 process 如何初始化、哪些 goroutine 會啟動、HTTP endpoint 如何註冊，以及程式如何關閉。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>用啟動流程理解 Go 應用結構</li>
<li>看懂 channel 與元件之間的資料流</li>
<li>理解 <code>context.WithCancel</code> 在關閉流程中的角色</li>
<li>判斷新增功能應該接在哪個生命週期位置</li>
</ol>
<hr>
<h2 id="觀察入口流程分成五段">【觀察】入口流程分成五段</h2>
<p>入口程式的核心責任是揭露應用如何啟動，而不是承載所有實作細節。一個稍具規模的 <code>main()</code> 可以切成五個區塊：</p>
<table>
  <thead>
      <tr>
          <th>區塊</th>
          <th>責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Runtime 與日誌設定</td>
          <td>設定記憶體限制、初始化 <code>slog</code></td>
      </tr>
      <tr>
          <td>環境設定</td>
          <td>讀取設定檔、環境變數與 port</td>
      </tr>
      <tr>
          <td>元件組裝</td>
          <td>建立 repository、worker、service 或 server</td>
      </tr>
      <tr>
          <td>背景工作</td>
          <td>啟動 worker、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> <a href="/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer</a> 或定時任務</td>
      </tr>
      <tr>
          <td>對外介面</td>
          <td>註冊 CLI command、HTTP endpoint 或 <a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> route</td>
      </tr>
  </tbody>
</table>
<p>這種切法讓入口同時保留完整脈絡，又不把所有實作細節塞進 <code>main()</code>。</p>
<h2 id="判讀main-的價值是揭露依賴關係">【判讀】<code>main()</code> 的價值是揭露依賴關係</h2>
<p><code>main()</code> 的核心價值是讓依賴關係可見。Go 專案常把依賴直接組裝在 <code>main()</code>，好處是維護者能直接看到應用骨架：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">events</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">Event</span><span class="p">,</span> <span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">notifications</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">Notification</span><span class="p">,</span> <span class="mi">128</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="nx">repo</span> <span class="o">:=</span> <span class="nf">NewNotificationRepository</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">worker</span> <span class="o">:=</span> <span class="nf">NewWorker</span><span class="p">(</span><span class="nx">repo</span><span class="p">,</span> <span class="nx">events</span><span class="p">,</span> <span class="nx">notifications</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">server</span> <span class="o">:=</span> <span class="nf">NewHTTPServer</span><span class="p">(</span><span class="nx">repo</span><span class="p">,</span> <span class="nx">notifications</span><span class="p">)</span></span></span></code></pre></div><p>這段程式揭露一個重要事實：<code>repo</code> 負責保存共享狀態，<code>worker</code> 負責處理背景事件，<code>server</code> 負責提供 HTTP 入口。資料如何流動，不需要先查框架設定就能看懂。</p>
<h2 id="策略用生命週期判斷功能應該放哪裡">【策略】用生命週期判斷功能應該放哪裡</h2>
<p>新增功能的核心判斷是：先確認它屬於哪一種生命週期，再決定接入位置。新增功能時，先判斷它屬於哪一種生命週期：</p>
<table>
  <thead>
      <tr>
          <th>新功能類型</th>
          <th>接入位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新 HTTP endpoint</td>
          <td>入口程式註冊 route，實作獨立 handler</td>
      </tr>
      <tr>
          <td>新背景事件來源</td>
          <td>新增 channel、worker 或 queue consumer</td>
      </tr>
      <tr>
          <td>新即時訊息 action</td>
          <td>message router 或連線管理元件</td>
      </tr>
      <tr>
          <td>新狀態欄位</td>
          <td>repository 更新與 model 擴展</td>
      </tr>
      <tr>
          <td>新診斷能力</td>
          <td>條件註冊 endpoint 或 <code>slog</code> 欄位</td>
      </tr>
  </tbody>
</table>
<p>這個判斷可以避免把功能塞進錯誤元件，造成後續難測與難改。</p>
<h2 id="執行完整啟動路徑">【執行】完整啟動路徑</h2>
<p>啟動路徑的核心用途是提供除錯地圖。一個有背景工作與 HTTP 介面的 Go 應用，啟動後的主要路徑可能如下：</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">main()
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  ├─ setup logger / runtime
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  ├─ create channels
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  ├─ create repository / worker / server
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  ├─ context.WithCancel()
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  ├─ go worker.Run(ctx)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  ├─ go startPeriodicSync(ctx)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  ├─ go server.Run(ctx)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  ├─ register /health
</span></span><span class="line"><span class="ln">10</span><span class="cl">  ├─ register /ws
</span></span><span class="line"><span class="ln">11</span><span class="cl">  ├─ register /events
</span></span><span class="line"><span class="ln">12</span><span class="cl">  ├─ go waitForShutdown(cancel, ...)
</span></span><span class="line"><span class="ln">13</span><span class="cl">  └─ http.Server.ListenAndServe()</span></span></code></pre></div><p>這份路徑也是除錯清單。當應用沒有產生預期輸出時，可以依序確認：輸入來源是否產生資料、worker 是否處理資料、對外介面是否有 client 或呼叫者、狀態資料是否被正確更新。</p>
]]></content:encoded></item></channel></rss>