<?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>Philosophy on Tarragon</title><link>https://tarrragon.github.io/blog/tags/philosophy/</link><description>Recent content in Philosophy on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 23 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/philosophy/index.xml" rel="self" type="application/rss+xml"/><item><title>0.1 Go 的簡單哲學與認知負擔</title><link>https://tarrragon.github.io/blog/go/00-philosophy/simplicity/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/simplicity/</guid><description>&lt;p>Go 的核心取捨是降低讀程式的人需要同時記住的事情。它不追求語法表現力最大化，而是追求團隊在多年後仍能快速判讀服務如何啟動、資料如何流動、錯誤如何處理。這些特性之所以重要，是因為它們直接支撐了 Go 在高併發服務、worker、長連線與事件處理場景中的可維護性。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>說明 Go 為什麼偏好顯式控制流程&lt;/li>
&lt;li>看懂入口程式中依賴組裝的意圖&lt;/li>
&lt;li>區分「程式碼短」和「認知負擔低」&lt;/li>
&lt;li>用 Go 的風格閱讀現有服務&lt;/li>
&lt;/ol>
&lt;h2 id="為什麼這章在第零章">為什麼這章在第零章&lt;/h2>
&lt;p>如果工作負載、架構與 runtime 條件已經顯示 Go 是合適選項，接下來就要理解 Go 為什麼會長成現在這種樣子。簡單、顯式、少魔法是讓高併發服務在多人維護時仍能被快速理解的工程策略。&lt;/p>
&lt;hr>
&lt;h2 id="觀察go-程式的入口通常很直">【觀察】Go 程式的入口通常很直&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="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;/p>
&lt;p>這種寫法特別適合服務型應用：當系統需要處理很多並發請求、背景工作或外部事件時，入口越清楚，就越容易確認誰負責什麼、失敗時會停在哪裡。&lt;/p>
&lt;h2 id="判讀簡單是少猜幾件事">【判讀】簡單是少猜幾件事&lt;/h2>
&lt;p>對維護者來說，入口程式的主要工作是回答三個問題：&lt;/p>
&lt;ol>
&lt;li>哪些元件存在？&lt;/li>
&lt;li>元件彼此怎麼連接？&lt;/li>
&lt;li>程式關閉時誰負責停止？&lt;/li>
&lt;/ol>
&lt;p>Go 偏好把這些答案留在表面。當你看到 &lt;code>NewWorker(repo, events, notifications)&lt;/code>，你不需要先理解框架規則，就能知道 worker 依賴 repository，從事件 channel 讀資料，輸出通知到另一個 channel。抽象的責任是讓資料流更容易判讀；抽象若讓讀者需要猜測背後規則，就削弱了 Go 在長期維護上的主要價值。&lt;/p>
&lt;h2 id="策略閱讀-go-程式先找資料流">【策略】閱讀 Go 程式先找資料流&lt;/h2>
&lt;p>讀 Go 應用時，可以用以下順序降低認知負擔：&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>1&lt;/td>
 &lt;td>process 從哪裡開始？&lt;/td>
 &lt;td>入口程式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>HTTP endpoint 在哪裡註冊？&lt;/td>
 &lt;td>route 註冊處&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>背景工作有哪些？&lt;/td>
 &lt;td>&lt;code>go ...&lt;/code> 呼叫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4&lt;/td>
 &lt;td>資料用什麼型別傳遞？&lt;/td>
 &lt;td>model 定義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5&lt;/td>
 &lt;td>共享狀態由誰保護？&lt;/td>
 &lt;td>repository 或狀態元件&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這個順序先建立系統地圖，再深入單一函式，避免一開始就陷入細節。&lt;/p>
&lt;h2 id="執行用入口程式畫出資料流">【執行】用入口程式畫出資料流&lt;/h2>
&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">file / HTTP / timer ──&amp;gt; events ──&amp;gt; Worker ──&amp;gt; notifications
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> Repository
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> HTTP API&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這張圖比逐行閱讀更重要。它先回答「系統如何運作」，再讓你回到個別函式確認細節。&lt;/p></description><content:encoded><![CDATA[<p>Go 的核心取捨是降低讀程式的人需要同時記住的事情。它不追求語法表現力最大化，而是追求團隊在多年後仍能快速判讀服務如何啟動、資料如何流動、錯誤如何處理。這些特性之所以重要，是因為它們直接支撐了 Go 在高併發服務、worker、長連線與事件處理場景中的可維護性。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>說明 Go 為什麼偏好顯式控制流程</li>
<li>看懂入口程式中依賴組裝的意圖</li>
<li>區分「程式碼短」和「認知負擔低」</li>
<li>用 Go 的風格閱讀現有服務</li>
</ol>
<h2 id="為什麼這章在第零章">為什麼這章在第零章</h2>
<p>如果工作負載、架構與 runtime 條件已經顯示 Go 是合適選項，接下來就要理解 Go 為什麼會長成現在這種樣子。簡單、顯式、少魔法是讓高併發服務在多人維護時仍能被快速理解的工程策略。</p>
<hr>
<h2 id="觀察go-程式的入口通常很直">【觀察】Go 程式的入口通常很直</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="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>這段程式沒有依賴注入框架，也沒有隱式容器。所有元件的建立順序、依賴關係、資料流向都直接寫在入口。</p>
<p>這種寫法特別適合服務型應用：當系統需要處理很多並發請求、背景工作或外部事件時，入口越清楚，就越容易確認誰負責什麼、失敗時會停在哪裡。</p>
<h2 id="判讀簡單是少猜幾件事">【判讀】簡單是少猜幾件事</h2>
<p>對維護者來說，入口程式的主要工作是回答三個問題：</p>
<ol>
<li>哪些元件存在？</li>
<li>元件彼此怎麼連接？</li>
<li>程式關閉時誰負責停止？</li>
</ol>
<p>Go 偏好把這些答案留在表面。當你看到 <code>NewWorker(repo, events, notifications)</code>，你不需要先理解框架規則，就能知道 worker 依賴 repository，從事件 channel 讀資料，輸出通知到另一個 channel。抽象的責任是讓資料流更容易判讀；抽象若讓讀者需要猜測背後規則，就削弱了 Go 在長期維護上的主要價值。</p>
<h2 id="策略閱讀-go-程式先找資料流">【策略】閱讀 Go 程式先找資料流</h2>
<p>讀 Go 應用時，可以用以下順序降低認知負擔：</p>
<table>
  <thead>
      <tr>
          <th>順序</th>
          <th>問題</th>
          <th>對應檔案</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>process 從哪裡開始？</td>
          <td>入口程式</td>
      </tr>
      <tr>
          <td>2</td>
          <td>HTTP endpoint 在哪裡註冊？</td>
          <td>route 註冊處</td>
      </tr>
      <tr>
          <td>3</td>
          <td>背景工作有哪些？</td>
          <td><code>go ...</code> 呼叫</td>
      </tr>
      <tr>
          <td>4</td>
          <td>資料用什麼型別傳遞？</td>
          <td>model 定義</td>
      </tr>
      <tr>
          <td>5</td>
          <td>共享狀態由誰保護？</td>
          <td>repository 或狀態元件</td>
      </tr>
  </tbody>
</table>
<p>這個順序先建立系統地圖，再深入單一函式，避免一開始就陷入細節。</p>
<h2 id="執行用入口程式畫出資料流">【執行】用入口程式畫出資料流</h2>
<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">file / HTTP / timer ──&gt; events ──&gt; Worker ──&gt; notifications
</span></span><span class="line"><span class="ln">2</span><span class="cl">                                      │
</span></span><span class="line"><span class="ln">3</span><span class="cl">                                      ▼
</span></span><span class="line"><span class="ln">4</span><span class="cl">                                  Repository
</span></span><span class="line"><span class="ln">5</span><span class="cl">                                      │
</span></span><span class="line"><span class="ln">6</span><span class="cl">                                      ▼
</span></span><span class="line"><span class="ln">7</span><span class="cl">                                   HTTP API</span></span></code></pre></div><p>這張圖比逐行閱讀更重要。它先回答「系統如何運作」，再讓你回到個別函式確認細節。</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>認知負擔：程式碼設計的核心目的</title><link>https://tarrragon.github.io/blog/python/00-philosophy/cognitive-load/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/cognitive-load/</guid><description>&lt;h2 id="什麼是認知負擔">什麼是認知負擔？&lt;/h2>
&lt;p>認知負擔（Cognitive Load）是心理學中的概念，指的是人腦在處理資訊時所承受的負擔量。&lt;/p>
&lt;h3 id="工作記憶的限制">工作記憶的限制&lt;/h3>
&lt;p>心理學家 George Miller 在 1956 年提出著名的「7 加減 2」法則：人類的工作記憶一次只能處理約 &lt;strong>5 到 9 個項目&lt;/strong>。&lt;/p>
&lt;p>這意味著當你閱讀程式碼時：&lt;/p>
&lt;ul>
&lt;li>如果需要同時記住超過 7 個變數的狀態，你會開始混淆&lt;/li>
&lt;li>如果需要追蹤超過 7 層的呼叫關係，你會迷失方向&lt;/li>
&lt;li>如果一個函式做超過 7 件事，你會難以理解它的目的&lt;/li>
&lt;/ul>
&lt;h3 id="程式碼閱讀中的認知負擔">程式碼閱讀中的認知負擔&lt;/h3>
&lt;p>閱讀程式碼時，以下情況會增加認知負擔：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 高認知負擔的程式碼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d&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">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">d&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">i&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;=&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">6&lt;/span>&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="o">&amp;gt;&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">8&lt;/span>&lt;span class="cl"> &lt;span class="n">r&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">i&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">reverse&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;/code>&lt;/pre>&lt;/div>&lt;p>閱讀這段程式碼時，你需要：&lt;/p>
&lt;ol>
&lt;li>記住 &lt;code>d&lt;/code> 是什麼（輸入資料）&lt;/li>
&lt;li>追蹤 &lt;code>r&lt;/code> 的狀態（結果列表）&lt;/li>
&lt;li>理解 &lt;code>i&lt;/code> 的結構（至少有 3 個元素的序列）&lt;/li>
&lt;li>計算 &lt;code>t&lt;/code> 的值（某種加權計算）&lt;/li>
&lt;li>記住過濾條件（三個條件）&lt;/li>
&lt;li>理解最終排序邏輯&lt;/li>
&lt;/ol>
&lt;p>這就是典型的高認知負擔程式碼。&lt;/p>
&lt;h2 id="核心論點所有原則的統一目的">核心論點：所有原則的統一目的&lt;/h2>
&lt;h3 id="clean-code-不是優美而是易讀">Clean Code 不是「優美」，而是「易讀」&lt;/h3>
&lt;p>很多人誤解 Clean 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">Clean Code 的真正目標是：讓程式碼能被人類輕鬆理解&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>優美的程式碼如果難以理解，就不是好的程式碼。樸素但清晰的程式碼，遠勝於巧妙但費解的程式碼。&lt;/p>
&lt;h3 id="無法讀懂的程式碼沒人會讀">無法讀懂的程式碼沒人會讀&lt;/h3>
&lt;p>這是一個殘酷的現實：&lt;/p>
&lt;ul>
&lt;li>如果程式碼太難讀，維護者會選擇重寫而非修改&lt;/li>
&lt;li>如果程式碼太難讀，除錯會變成猜測遊戲&lt;/li>
&lt;li>如果程式碼太難讀，知識無法傳承&lt;/li>
&lt;/ul>
&lt;h3 id="drysolid命名規範--降低認知負擔的不同策略">DRY、SOLID、命名規範 = 降低認知負擔的不同策略&lt;/h3>
&lt;p>讓我們重新審視這些經典原則：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>原則&lt;/th>
 &lt;th>傳統解釋&lt;/th>
 &lt;th>認知負擔視角&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>DRY&lt;/td>
 &lt;td>不要重複自己&lt;/td>
 &lt;td>讀者只需要理解一次，減少記憶負擔&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>單一職責&lt;/td>
 &lt;td>一個類別只做一件事&lt;/td>
 &lt;td>讀者一次只需要理解一個概念&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開放封閉&lt;/td>
 &lt;td>對擴展開放，對修改封閉&lt;/td>
 &lt;td>讀者不需要理解整個系統就能擴展&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>依賴反轉&lt;/td>
 &lt;td>依賴抽象而非具體&lt;/td>
 &lt;td>讀者可以忽略實作細節&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>命名規範&lt;/td>
 &lt;td>使用有意義的名稱&lt;/td>
 &lt;td>讀者不需要追溯定義就能理解&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>它們的共同目標都是：降低閱讀者的認知負擔。&lt;/p>
&lt;h2 id="認知負擔的來源">認知負擔的來源&lt;/h2>
&lt;h3 id="1-需要記住前面發生什麼事">1. 需要記住前面發生什麼事&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 高認知負擔：需要記住 data 經歷了什麼轉換&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">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_raw_data&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">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">filter_invalid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">normalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enrich&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">aggregate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 低認知負擔：每步都有清晰的命名&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">raw_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_raw_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">valid_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">filter_invalid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">raw_data&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">normalized_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">normalize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">valid_data&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">enriched_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enrich&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">aggregate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">enriched_data&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-需要追蹤變數經歷的轉換">2. 需要追蹤變數經歷的轉換&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"># 高認知負擔：temp 到底是什麼？&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">temp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">user_input&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"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">temp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">temp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">temp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">temp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">temp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;[^a-z_]&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">temp&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 低認知負擔：每個變數都說明自己是什麼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">trimmed_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">user_input&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"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">lowercase_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">trimmed_input&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">underscored_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lowercase_input&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="n">clean_identifier&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;[^a-z_]&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">underscored_input&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-需要理解隱藏的狀態變化">3. 需要理解隱藏的狀態變化&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"># 高認知負擔：process() 會修改什麼？&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">class&lt;/span> &lt;span class="nc">DataProcessor&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">def&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_validate&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 可能修改 self.errors?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_transform&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 可能修改 self.data?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_save&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 可能修改 self.saved?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 低認知負擔：回傳值明確說明結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">DataProcessor&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">def&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ProcessResult&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">errors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&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="n">errors&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ProcessResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">errors&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">errors&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">transformed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_transform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&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">save_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">transformed&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ProcessResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">saved_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">save_result&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-需要跳轉到其他地方才能理解當前程式碼">4. 需要跳轉到其他地方才能理解當前程式碼&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"># 高認知負擔：需要跳到 MAGIC_VALUE 的定義&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">score&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">MAGIC_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="k">return&lt;/span> &lt;span class="s2">&amp;#34;pass&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 低認知負擔：直接說明意圖&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">PASSING_SCORE_THRESHOLD&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">score&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">PASSING_SCORE_THRESHOLD&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="s2">&amp;#34;pass&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="降低認知負擔的原則">降低認知負擔的原則&lt;/h2>
&lt;h3 id="原則一在當下就能理解">原則一：在當下就能理解&lt;/h3>
&lt;p>好的程式碼不需要讀者記住之前發生的事情：&lt;/p></description><content:encoded><![CDATA[<h2 id="什麼是認知負擔">什麼是認知負擔？</h2>
<p>認知負擔（Cognitive Load）是心理學中的概念，指的是人腦在處理資訊時所承受的負擔量。</p>
<h3 id="工作記憶的限制">工作記憶的限制</h3>
<p>心理學家 George Miller 在 1956 年提出著名的「7 加減 2」法則：人類的工作記憶一次只能處理約 <strong>5 到 9 個項目</strong>。</p>
<p>這意味著當你閱讀程式碼時：</p>
<ul>
<li>如果需要同時記住超過 7 個變數的狀態，你會開始混淆</li>
<li>如果需要追蹤超過 7 層的呼叫關係，你會迷失方向</li>
<li>如果一個函式做超過 7 件事，你會難以理解它的目的</li>
</ul>
<h3 id="程式碼閱讀中的認知負擔">程式碼閱讀中的認知負擔</h3>
<p>閱讀程式碼時，以下情況會增加認知負擔：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 高認知負擔的程式碼</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">r</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">d</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">i</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">i</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">!=</span> <span class="s2">&#34;&#34;</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">            <span class="n">t</span> <span class="o">=</span> <span class="n">i</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">i</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">            <span class="k">if</span> <span class="n">t</span> <span class="o">&gt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">                <span class="n">r</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">i</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">t</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div><p>閱讀這段程式碼時，你需要：</p>
<ol>
<li>記住 <code>d</code> 是什麼（輸入資料）</li>
<li>追蹤 <code>r</code> 的狀態（結果列表）</li>
<li>理解 <code>i</code> 的結構（至少有 3 個元素的序列）</li>
<li>計算 <code>t</code> 的值（某種加權計算）</li>
<li>記住過濾條件（三個條件）</li>
<li>理解最終排序邏輯</li>
</ol>
<p>這就是典型的高認知負擔程式碼。</p>
<h2 id="核心論點所有原則的統一目的">核心論點：所有原則的統一目的</h2>
<h3 id="clean-code-不是優美而是易讀">Clean Code 不是「優美」，而是「易讀」</h3>
<p>很多人誤解 Clean 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">Clean Code 的真正目標是：讓程式碼能被人類輕鬆理解</span></span></code></pre></div><p>優美的程式碼如果難以理解，就不是好的程式碼。樸素但清晰的程式碼，遠勝於巧妙但費解的程式碼。</p>
<h3 id="無法讀懂的程式碼沒人會讀">無法讀懂的程式碼沒人會讀</h3>
<p>這是一個殘酷的現實：</p>
<ul>
<li>如果程式碼太難讀，維護者會選擇重寫而非修改</li>
<li>如果程式碼太難讀，除錯會變成猜測遊戲</li>
<li>如果程式碼太難讀，知識無法傳承</li>
</ul>
<h3 id="drysolid命名規範--降低認知負擔的不同策略">DRY、SOLID、命名規範 = 降低認知負擔的不同策略</h3>
<p>讓我們重新審視這些經典原則：</p>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>傳統解釋</th>
          <th>認知負擔視角</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DRY</td>
          <td>不要重複自己</td>
          <td>讀者只需要理解一次，減少記憶負擔</td>
      </tr>
      <tr>
          <td>單一職責</td>
          <td>一個類別只做一件事</td>
          <td>讀者一次只需要理解一個概念</td>
      </tr>
      <tr>
          <td>開放封閉</td>
          <td>對擴展開放，對修改封閉</td>
          <td>讀者不需要理解整個系統就能擴展</td>
      </tr>
      <tr>
          <td>依賴反轉</td>
          <td>依賴抽象而非具體</td>
          <td>讀者可以忽略實作細節</td>
      </tr>
      <tr>
          <td>命名規範</td>
          <td>使用有意義的名稱</td>
          <td>讀者不需要追溯定義就能理解</td>
      </tr>
  </tbody>
</table>
<p>它們的共同目標都是：降低閱讀者的認知負擔。</p>
<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"># 高認知負擔：需要記住 data 經歷了什麼轉換</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">get_raw_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">filter_invalid</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">enrich</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">aggregate</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 低認知負擔：每步都有清晰的命名</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">raw_data</span> <span class="o">=</span> <span class="n">get_raw_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">valid_data</span> <span class="o">=</span> <span class="n">filter_invalid</span><span class="p">(</span><span class="n">raw_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">normalized_data</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">valid_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">enriched_data</span> <span class="o">=</span> <span class="n">enrich</span><span class="p">(</span><span class="n">normalized_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">aggregate</span><span class="p">(</span><span class="n">enriched_data</span><span class="p">)</span></span></span></code></pre></div><h3 id="2-需要追蹤變數經歷的轉換">2. 需要追蹤變數經歷的轉換</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 高認知負擔：temp 到底是什麼？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">temp</span> <span class="o">=</span> <span class="n">user_input</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">temp</span> <span class="o">=</span> <span class="n">temp</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">temp</span> <span class="o">=</span> <span class="n">temp</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="s2">&#34;_&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">temp</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;[^a-z_]&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">temp</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 低認知負擔：每個變數都說明自己是什麼</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">trimmed_input</span> <span class="o">=</span> <span class="n">user_input</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">lowercase_input</span> <span class="o">=</span> <span class="n">trimmed_input</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">underscored_input</span> <span class="o">=</span> <span class="n">lowercase_input</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="s2">&#34;_&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">clean_identifier</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;[^a-z_]&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">underscored_input</span><span class="p">)</span></span></span></code></pre></div><h3 id="3-需要理解隱藏的狀態變化">3. 需要理解隱藏的狀態變化</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 高認知負擔：process() 會修改什麼？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">DataProcessor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validate</span><span class="p">()</span>      <span class="c1"># 可能修改 self.errors?</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_transform</span><span class="p">()</span>     <span class="c1"># 可能修改 self.data?</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_save</span><span class="p">()</span>          <span class="c1"># 可能修改 self.saved?</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 低認知負擔：回傳值明確說明結果</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">DataProcessor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProcessResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">errors</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">data</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="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="k">return</span> <span class="n">ProcessResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">transformed</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_transform</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">save_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_save</span><span class="p">(</span><span class="n">transformed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="n">ProcessResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">saved_path</span><span class="o">=</span><span class="n">save_result</span><span class="p">)</span></span></span></code></pre></div><h3 id="4-需要跳轉到其他地方才能理解當前程式碼">4. 需要跳轉到其他地方才能理解當前程式碼</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"># 高認知負擔：需要跳到 MAGIC_VALUE 的定義</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="n">score</span> <span class="o">&gt;</span> <span class="n">MAGIC_VALUE</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="s2">&#34;pass&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 低認知負擔：直接說明意圖</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">PASSING_SCORE_THRESHOLD</span> <span class="o">=</span> <span class="mi">60</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">if</span> <span class="n">score</span> <span class="o">&gt;</span> <span class="n">PASSING_SCORE_THRESHOLD</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="s2">&#34;pass&#34;</span></span></span></code></pre></div><h2 id="降低認知負擔的原則">降低認知負擔的原則</h2>
<h3 id="原則一在當下就能理解">原則一：在當下就能理解</h3>
<p>好的程式碼不需要讀者記住之前發生的事情：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：需要記住 user 是什麼</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">user</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">user</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">and</span> <span class="n">user</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">18</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">return</span> <span class="n">user</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 好：當下就能理解</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">get_adult_user_name</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="n">User</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"> 8</span><span class="cl">    <span class="k">if</span> <span class="n">user</span><span class="o">.</span><span class="n">is_active</span> <span class="ow">and</span> <span class="n">user</span><span class="o">.</span><span class="n">age</span> <span class="o">&gt;</span> <span class="mi">18</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">user</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="kc">None</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"># 不好：需要註解才能理解</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">if</span> <span class="n">u</span><span class="o">.</span><span class="n">r</span> <span class="o">&gt;=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="n">u</span><span class="o">.</span><span class="n">s</span> <span class="o">==</span> <span class="s1">&#39;a&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 好：程式碼本身就是說明</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">if</span> <span class="n">user</span><span class="o">.</span><span class="n">role_level</span> <span class="o">&gt;=</span> <span class="n">ADMIN_LEVEL</span> <span class="ow">and</span> <span class="n">user</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">UserStatus</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">pass</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"># 不好：get 通常不應該修改狀態</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">get_user_count</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_refresh_cache</span><span class="p">()</span>  <span class="c1"># 意外的副作用！</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_users</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"># 好：get 只做讀取</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">get_user_count</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_users</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">refresh_and_get_user_count</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_refresh_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_users</span><span class="p">)</span></span></span></code></pre></div><h2 id="實際案例hook-系統重構">實際案例：Hook 系統重構</h2>
<p>讓我們看一個實際的重構案例。</p>
<h3 id="重構前高認知負擔">重構前（高認知負擔）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_hook</span><span class="p">(</span><span class="n">path</span><span class="p">):</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">path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="n">c</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"># 檢查 shebang</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">c</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#!&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;no shebang&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 解析配置</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">cfg</span> <span class="o">=</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;.claude/config.yaml&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 驗證</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">for</span> <span class="n">h</span> <span class="ow">in</span> <span class="n">cfg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;hooks&#34;</span><span class="p">,</span> <span class="p">[]):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">if</span> <span class="n">h</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;path&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;not found&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">access</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">X_OK</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="s2">&#34;not executable&#34;</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 class="p">,</span> <span class="s2">&#34;ok&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;not registered&#34;</span></span></span></code></pre></div><p>讀者需要：</p>
<ul>
<li>記住 <code>c</code> 是檔案內容</li>
<li>理解為什麼要檢查 shebang</li>
<li>追蹤 <code>cfg</code> 的結構</li>
<li>理解 <code>h</code> 和 <code>path</code> 的關係</li>
</ul>
<h3 id="重構後低認知負擔">重構後（低認知負擔）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="n">load_hook_config</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_validator</span> <span class="kn">import</span> <span class="n">validate_hook_file</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_hook</span><span class="p">(</span><span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="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"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    檢查指定的 Hook 檔案是否有效。
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        (是否有效, 訊息)
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># 載入配置</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_hook_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 檢查是否已註冊</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">config</span><span class="o">.</span><span class="n">is_registered</span><span class="p">(</span><span class="n">hook_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;Hook 未在配置中註冊&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 驗證檔案</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">validation_result</span> <span class="o">=</span> <span class="n">validate_hook_file</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">validation_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">,</span> <span class="n">validation_result</span><span class="o">.</span><span class="n">message</span></span></span></code></pre></div><p>改善之處：</p>
<ul>
<li>函式名稱說明目的</li>
<li>型別提示說明輸入輸出</li>
<li>每個步驟都有清晰的意圖</li>
<li>複雜邏輯封裝在專門的函式中</li>
</ul>
<h2 id="自我檢查清單">自我檢查清單</h2>
<p>閱讀或撰寫程式碼時，問自己這些問題：</p>
<ul>
<li><input disabled="" type="checkbox"> 讀者需要記住幾個變數的狀態？（應該少於 5 個）</li>
<li><input disabled="" type="checkbox"> 讀者需要追蹤多少層呼叫？（應該少於 3 層）</li>
<li><input disabled="" type="checkbox"> 讀者能在當下理解這段程式碼嗎？（不需要往回看）</li>
<li><input disabled="" type="checkbox"> 變數名稱是否說明它是什麼？（不是它怎麼來的）</li>
<li><input disabled="" type="checkbox"> 函式名稱是否說明它做什麼？（不是它怎麼做的）</li>
</ul>
<h2 id="小結">小結</h2>
<p>認知負擔是程式碼品質的終極度量標準。</p>
<p>所有的設計原則、最佳實踐、重構技巧，都可以用一個問題來檢驗：</p>
<blockquote>
<p>這樣做是否降低了閱讀者的認知負擔？</p></blockquote>
<p>當你面對設計決策時，不要問「這樣是否符合 DRY」或「這樣是否符合 SOLID」，而是問：</p>
<blockquote>
<p>這樣寫的話，下一個讀這段程式碼的人（可能是三個月後的你自己），需要記住多少東西才能理解它？</p></blockquote>
<p>這就是程式碼設計的核心目的。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事</a> - 如何用命名降低認知負擔</li>
<li><a href="/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔</a> - SOLID 原則的認知負擔詮釋</li>
</ul>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>Miller, G. A. (1956). &ldquo;The Magical Number Seven, Plus or Minus Two&rdquo;</li>
<li>Martin, R. C. (2008). &ldquo;Clean Code: A Handbook of Agile Software Craftsmanship&rdquo;</li>
</ul>
]]></content:encoded></item><item><title>0.2 組合優先：小介面與明確依賴</title><link>https://tarrragon.github.io/blog/go/00-philosophy/composition/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/composition/</guid><description>&lt;p>Go 組合的核心原則是用小型型別與小介面拼出行為。程式需要什麼能力，就依賴那個能力；型別擁有哪些資料，就把資料明確放在 struct 裡。這種設計讓高併發服務更容易拆解責任，也更容易在選型成立時維持邊界清楚。&lt;/p>
&lt;h2 id="為什麼這章在第零章">為什麼這章在第零章&lt;/h2>
&lt;p>當你的工作負載本來就適合 Go 時，真正需要確認的就不只是語法，而是 Go 如何幫你把服務邊界維持清楚。組合優先讓 &lt;code>main()&lt;/code>、constructor、handler、worker 與 repository 的責任能被直接看見，這是 Go 在服務型專案中很重要的可維護性來源。&lt;/p>
&lt;h2 id="組合先描述擁有什麼">組合先描述擁有什麼&lt;/h2>
&lt;p>struct 的核心責任是把一組資料與依賴放在同一個明確邊界內。Go 不用 class inheritance 表達「某個型別繼承另一個型別」，而是用欄位組合出需要的結構。&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">Logger&lt;/span> &lt;span class="kd">interface&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="nf">Info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">message&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">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">type&lt;/span> &lt;span class="nx">Server&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">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">addr&lt;/span> &lt;span class="kt">string&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">logger&lt;/span> &lt;span class="nx">Logger&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>Server&lt;/code> 擁有一個地址，也依賴一個 logger。這些資訊都在欄位上直接呈現，讀者不需要追蹤隱式容器或父類別初始化順序。&lt;/p>
&lt;p>當依賴變多時，struct 仍然應該只保留這個型別真正需要的依賴。把整個 application container 塞進 struct，通常會讓依賴邊界變模糊。&lt;/p>
&lt;h2 id="小介面先描述需要什麼">小介面先描述需要什麼&lt;/h2>
&lt;p>interface 的核心責任是描述呼叫端需要的行為。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">UserFinder&lt;/span> &lt;span class="kd">interface&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="nf">FindUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&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">User&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">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">type&lt;/span> &lt;span class="nx">UserHandler&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">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">finder&lt;/span> &lt;span class="nx">UserFinder&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>UserHandler&lt;/code> 不需要知道資料來自資料庫、快取或遠端 API。它只需要「可以用 id 找使用者」這個能力，因此介面只放 &lt;code>FindUser&lt;/code>。&lt;/p>
&lt;p>介面應由替換、測試或隔離需求推動。只有一個具體型別，而且沒有測試替身或替換需求時，先直接依賴具體型別通常更簡單。過度抽象的代價是把原本簡單的依賴藏成難追蹤的間接關係，反而比直接依賴更難閱讀。&lt;/p>
&lt;h2 id="依賴由外層組裝">依賴由外層組裝&lt;/h2>
&lt;p>Go 應用組裝依賴的核心策略是讓外層建立具體型別，內層只接收自己需要的依賴。常見位置是 &lt;code>main()&lt;/code> 或專門的 constructor。&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">NewUserHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">finder&lt;/span> &lt;span class="nx">UserFinder&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">UserHandler&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">UserHandler&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">finder&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">finder&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;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">db&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewDatabase&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;postgres://localhost/app&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">repository&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewUserRepository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nx">handler&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewUserHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">repository&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="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;/users/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ServeHTTP&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>這段程式讓資料流與依賴關係保持可見：repository 依賴 db，handler 依賴 repository 的查詢能力。Go 的組合方式偏好把這些關係寫出來，而不是藏在 framework magic 裡。&lt;/p>
&lt;h2 id="行為可以用-embedding-重用">行為可以用 embedding 重用&lt;/h2>
&lt;p>embedding 的核心用途是把一個型別的欄位或方法提升到外層型別。它是組合工具；若需要表達明確擁有關係，named field 會比繼承式心智模型更清楚。&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">AuditFields&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">CreatedAt&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="nx">UpdatedAt&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Time&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="kd">type&lt;/span> &lt;span class="nx">User&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nx">ID&lt;/span> &lt;span class="kt">string&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">Email&lt;/span> &lt;span class="kt">string&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">AuditFields&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>User&lt;/code> 透過 embedding 擁有 &lt;code>CreatedAt&lt;/code> 與 &lt;code>UpdatedAt&lt;/code>。這適合重用資料欄位，但不代表 &lt;code>User&lt;/code> 在概念上「繼承」了 &lt;code>AuditFields&lt;/code> 的完整行為。&lt;/p>
&lt;p>embedding 應該用在語意自然的地方。若提升方法會讓外層型別出現不該公開的能力，明確寫欄位名稱通常更安全。&lt;/p>
&lt;h2 id="組合讓測試替換更自然">組合讓測試替換更自然&lt;/h2>
&lt;p>組合的測試價值是可以替換依賴，而不需要啟動整個系統。只要 production code 依賴小介面，測試就能提供 fake。&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">fakeUserFinder&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">user&lt;/span> &lt;span class="nx">User&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">err&lt;/span> &lt;span class="kt">error&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">f&lt;/span> &lt;span class="nx">fakeUserFinder&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">FindUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&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">User&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">f&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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="nx">f&lt;/span>&lt;span class="p">.&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;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">f&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">user&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">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>測試 handler 時，可以把 &lt;code>fakeUserFinder&lt;/code> 傳進去，專注檢查 HTTP response。這種替換的目標是讓測試只覆蓋當前邊界的行為。&lt;/p></description><content:encoded><![CDATA[<p>Go 組合的核心原則是用小型型別與小介面拼出行為。程式需要什麼能力，就依賴那個能力；型別擁有哪些資料，就把資料明確放在 struct 裡。這種設計讓高併發服務更容易拆解責任，也更容易在選型成立時維持邊界清楚。</p>
<h2 id="為什麼這章在第零章">為什麼這章在第零章</h2>
<p>當你的工作負載本來就適合 Go 時，真正需要確認的就不只是語法，而是 Go 如何幫你把服務邊界維持清楚。組合優先讓 <code>main()</code>、constructor、handler、worker 與 repository 的責任能被直接看見，這是 Go 在服務型專案中很重要的可維護性來源。</p>
<h2 id="組合先描述擁有什麼">組合先描述擁有什麼</h2>
<p>struct 的核心責任是把一組資料與依賴放在同一個明確邊界內。Go 不用 class inheritance 表達「某個型別繼承另一個型別」，而是用欄位組合出需要的結構。</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">Logger</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nf">Info</span><span class="p">(</span><span class="nx">message</span> <span class="kt">string</span><span class="p">)</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">type</span> <span class="nx">Server</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">addr</span>   <span class="kt">string</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"> <span class="nx">logger</span> <span class="nx">Logger</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>Server</code> 擁有一個地址，也依賴一個 logger。這些資訊都在欄位上直接呈現，讀者不需要追蹤隱式容器或父類別初始化順序。</p>
<p>當依賴變多時，struct 仍然應該只保留這個型別真正需要的依賴。把整個 application container 塞進 struct，通常會讓依賴邊界變模糊。</p>
<h2 id="小介面先描述需要什麼">小介面先描述需要什麼</h2>
<p>interface 的核心責任是描述呼叫端需要的行為。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">UserFinder</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nf">FindUser</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">User</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</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">type</span> <span class="nx">UserHandler</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">finder</span> <span class="nx">UserFinder</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>UserHandler</code> 不需要知道資料來自資料庫、快取或遠端 API。它只需要「可以用 id 找使用者」這個能力，因此介面只放 <code>FindUser</code>。</p>
<p>介面應由替換、測試或隔離需求推動。只有一個具體型別，而且沒有測試替身或替換需求時，先直接依賴具體型別通常更簡單。過度抽象的代價是把原本簡單的依賴藏成難追蹤的間接關係，反而比直接依賴更難閱讀。</p>
<h2 id="依賴由外層組裝">依賴由外層組裝</h2>
<p>Go 應用組裝依賴的核心策略是讓外層建立具體型別，內層只接收自己需要的依賴。常見位置是 <code>main()</code> 或專門的 constructor。</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">NewUserHandler</span><span class="p">(</span><span class="nx">finder</span> <span class="nx">UserFinder</span><span class="p">)</span> <span class="nx">UserHandler</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">UserHandler</span><span class="p">{</span><span class="nx">finder</span><span class="p">:</span> <span class="nx">finder</span><span class="p">}</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">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">db</span> <span class="o">:=</span> <span class="nf">NewDatabase</span><span class="p">(</span><span class="s">&#34;postgres://localhost/app&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">repository</span> <span class="o">:=</span> <span class="nf">NewUserRepository</span><span class="p">(</span><span class="nx">db</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">handler</span> <span class="o">:=</span> <span class="nf">NewUserHandler</span><span class="p">(</span><span class="nx">repository</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="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/users/&#34;</span><span class="p">,</span> <span class="nx">handler</span><span class="p">.</span><span class="nx">ServeHTTP</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>這段程式讓資料流與依賴關係保持可見：repository 依賴 db，handler 依賴 repository 的查詢能力。Go 的組合方式偏好把這些關係寫出來，而不是藏在 framework magic 裡。</p>
<h2 id="行為可以用-embedding-重用">行為可以用 embedding 重用</h2>
<p>embedding 的核心用途是把一個型別的欄位或方法提升到外層型別。它是組合工具；若需要表達明確擁有關係，named field 會比繼承式心智模型更清楚。</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">AuditFields</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">CreatedAt</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">UpdatedAt</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Time</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="kd">type</span> <span class="nx">User</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">ID</span>    <span class="kt">string</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">Email</span> <span class="kt">string</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">AuditFields</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>User</code> 透過 embedding 擁有 <code>CreatedAt</code> 與 <code>UpdatedAt</code>。這適合重用資料欄位，但不代表 <code>User</code> 在概念上「繼承」了 <code>AuditFields</code> 的完整行為。</p>
<p>embedding 應該用在語意自然的地方。若提升方法會讓外層型別出現不該公開的能力，明確寫欄位名稱通常更安全。</p>
<h2 id="組合讓測試替換更自然">組合讓測試替換更自然</h2>
<p>組合的測試價值是可以替換依賴，而不需要啟動整個系統。只要 production code 依賴小介面，測試就能提供 fake。</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">fakeUserFinder</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">user</span> <span class="nx">User</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">err</span>  <span class="kt">error</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="kd">func</span> <span class="p">(</span><span class="nx">f</span> <span class="nx">fakeUserFinder</span><span class="p">)</span> <span class="nf">FindUser</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">User</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"> 7</span><span class="cl"> <span class="k">if</span> <span class="nx">f</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"> 8</span><span class="cl">     <span class="k">return</span> <span class="nx">User</span><span class="p">{},</span> <span class="nx">f</span><span class="p">.</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><span class="line"><span class="ln">10</span><span class="cl"> <span class="k">return</span> <span class="nx">f</span><span class="p">.</span><span class="nx">user</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>測試 handler 時，可以把 <code>fakeUserFinder</code> 傳進去，專注檢查 HTTP response。這種替換的目標是讓測試只覆蓋當前邊界的行為。</p>
]]></content:encoded></item><item><title>命名的藝術：讓程式碼說故事</title><link>https://tarrragon.github.io/blog/python/00-philosophy/naming-art/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/naming-art/</guid><description>&lt;h2 id="程式碼是一個故事">程式碼是一個故事&lt;/h2>
&lt;p>好的程式碼讀起來應該像一個故事，有主角（變數）、有動作（函式）、有情節（流程）。&lt;/p>
&lt;h3 id="someone-bring-something-to-do-what">&amp;ldquo;someone bring something to do what&amp;rdquo;&lt;/h3>
&lt;p>想像你在描述一個場景：&lt;/p>
&lt;blockquote>
&lt;p>「使用者提交表單，系統驗證資料，然後儲存到資料庫」&lt;/p>&lt;/blockquote>
&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="n">user_submitted_form&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">receive_form_submission&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&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">validated_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate_form_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_submitted_form&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">save_to_database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validated_data&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-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">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&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">v&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">check&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="something-transfer-to-some-what">&amp;ldquo;something transfer to some what&amp;rdquo;&lt;/h3>
&lt;p>另一種敘事模式是描述資料的轉換：&lt;/p>
&lt;blockquote>
&lt;p>「原始輸入轉換成清理過的格式，再轉換成最終輸出」&lt;/p>&lt;/blockquote>





&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">raw_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_user_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="n">cleaned_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sanitize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">raw_input&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">formatted_output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">format_for_display&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cleaned_input&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="讀者應該能像讀故事一樣理解程式碼">讀者應該能像讀故事一樣理解程式碼&lt;/h3>
&lt;p>如果讀者需要：&lt;/p>
&lt;ul>
&lt;li>往回翻看變數定義&lt;/li>
&lt;li>查閱文件理解函式功能&lt;/li>
&lt;li>猜測縮寫的含義&lt;/li>
&lt;/ul>
&lt;p>那就不是好的故事。好的故事讓讀者自然地跟著情節走。&lt;/p>
&lt;h2 id="變數命名的藝術">變數命名的藝術&lt;/h2>
&lt;h3 id="壞名稱的特徵">壞名稱的特徵&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="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">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_data&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">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 壞：過於通用，無法區分&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">data2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_order_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">temp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">temp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 壞：誤導性的名稱&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="n">user_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user&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="好名稱的特徵">好名稱的特徵&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">user_profile&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user_profile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">current_timestamp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">validated_items&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">item_index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：能區分不同用途&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">user_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n">order_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_order_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">merged_report&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">merge_user_and_orders&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">order_data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：名稱和實際內容一致&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">active_user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_active_user&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 單數名稱，回傳單一用戶&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="n">active_users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_active_users&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="命名原則說明這是什麼不是怎麼來的">命名原則：說明「這是什麼」，不是「怎麼來的」&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">config_from_yaml&lt;/span> &lt;span class="o">=&lt;/span> &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">3&lt;/span>&lt;span class="cl">&lt;span class="n">user_after_validation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&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"># 好：名稱說明內容&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">app_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">valid_user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="布林變數的命名">布林變數的命名&lt;/h3>
&lt;p>布林變數應該讀起來像一個問句的答案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 不好：不清楚是什麼意思&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">user_status&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">3&lt;/span>&lt;span class="cl">&lt;span class="n">file_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：讀起來像問句的答案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">is_user_active&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span> &lt;span class="c1"># &amp;#34;Is user active?&amp;#34; - Yes&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">has_valid_license&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span> &lt;span class="c1"># &amp;#34;Has valid license?&amp;#34; - No&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">can_edit_document&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span> &lt;span class="c1"># &amp;#34;Can edit document?&amp;#34; - Yes&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">should_retry&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span> &lt;span class="c1"># &amp;#34;Should retry?&amp;#34; - No&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="集合類型的命名">集合類型的命名&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 不好：不清楚是單一還是多個&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_all_users&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 回傳列表，但名稱是單數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">user_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 回傳單一用戶，但名稱暗示列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：名稱反映結構&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_all_users&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 複數 = 列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 單數 = 單一物件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="n">user_ids&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_all_user_ids&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 複數 + 型別提示&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="n">user_id_to_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">build_user_map&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;h2 id="函式命名的藝術">函式命名的藝術&lt;/h2>
&lt;h3 id="壞名稱的特徵-1">壞名稱的特徵&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="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">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">request&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">do_something&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">manage_users&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 壞：不清楚會做什麼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">user_operation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">action&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">data_stuff&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d&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">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="好名稱的特徵-1">好名稱的特徵&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="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">validate_user_input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_input&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">extract_branch_name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">git_output&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">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">format_error_message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">calculate_total_price&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">OrderItem&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Decimal&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">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="命名原則說明做什麼不是怎麼做">命名原則：說明「做什麼」，不是「怎麼做」&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 不好：名稱洩漏實作細節&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">loop_through_and_sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">numbers&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">use_regex_to_find_emails&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 好：名稱說明意圖&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">calculate_sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">numbers&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">extract_email_addresses&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="常見動詞模式">常見動詞模式&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>動詞&lt;/th>
 &lt;th>使用場景&lt;/th>
 &lt;th>範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>get&lt;/code>&lt;/td>
 &lt;td>取得現有的值&lt;/td>
 &lt;td>&lt;code>get_user_name()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>set&lt;/code>&lt;/td>
 &lt;td>設定值&lt;/td>
 &lt;td>&lt;code>set_user_name()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>create&lt;/code>&lt;/td>
 &lt;td>建立新物件&lt;/td>
 &lt;td>&lt;code>create_user()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>build&lt;/code>&lt;/td>
 &lt;td>組裝複雜物件&lt;/td>
 &lt;td>&lt;code>build_report()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>calculate&lt;/code>&lt;/td>
 &lt;td>計算數值&lt;/td>
 &lt;td>&lt;code>calculate_total()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>validate&lt;/code>&lt;/td>
 &lt;td>驗證資料&lt;/td>
 &lt;td>&lt;code>validate_input()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>parse&lt;/code>&lt;/td>
 &lt;td>解析文字&lt;/td>
 &lt;td>&lt;code>parse_config()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>format&lt;/code>&lt;/td>
 &lt;td>格式化輸出&lt;/td>
 &lt;td>&lt;code>format_date()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>convert&lt;/code>&lt;/td>
 &lt;td>轉換型別&lt;/td>
 &lt;td>&lt;code>convert_to_json()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>extract&lt;/code>&lt;/td>
 &lt;td>從資料中提取&lt;/td>
 &lt;td>&lt;code>extract_ids()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>filter&lt;/code>&lt;/td>
 &lt;td>過濾資料&lt;/td>
 &lt;td>&lt;code>filter_active_users()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>find&lt;/code>&lt;/td>
 &lt;td>尋找符合條件的&lt;/td>
 &lt;td>&lt;code>find_user_by_email()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>is/has/can&lt;/code>&lt;/td>
 &lt;td>布林判斷&lt;/td>
 &lt;td>&lt;code>is_valid()&lt;/code>, &lt;code>has_permission()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="對稱命名">對稱命名&lt;/h3>
&lt;p>相關的函式應該有對稱的命名：&lt;/p></description><content:encoded><![CDATA[<h2 id="程式碼是一個故事">程式碼是一個故事</h2>
<p>好的程式碼讀起來應該像一個故事，有主角（變數）、有動作（函式）、有情節（流程）。</p>
<h3 id="someone-bring-something-to-do-what">&ldquo;someone bring something to do what&rdquo;</h3>
<p>想像你在描述一個場景：</p>
<blockquote>
<p>「使用者提交表單，系統驗證資料，然後儲存到資料庫」</p></blockquote>
<p>這就是一個故事。好的程式碼應該能像這樣被閱讀：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 可以像故事一樣閱讀的程式碼</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">user_submitted_form</span> <span class="o">=</span> <span class="n">receive_form_submission</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">validated_data</span> <span class="o">=</span> <span class="n">validate_form_data</span><span class="p">(</span><span class="n">user_submitted_form</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">save_to_database</span><span class="p">(</span><span class="n">validated_data</span><span class="p">)</span></span></span></code></pre></div><p>而不是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 需要解謎的程式碼</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">get</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">v</span> <span class="o">=</span> <span class="n">check</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">save</span><span class="p">(</span><span class="n">v</span><span class="p">)</span></span></span></code></pre></div><h3 id="something-transfer-to-some-what">&ldquo;something transfer to some what&rdquo;</h3>
<p>另一種敘事模式是描述資料的轉換：</p>
<blockquote>
<p>「原始輸入轉換成清理過的格式，再轉換成最終輸出」</p></blockquote>





<div class="highlight"><pre 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">raw_input</span> <span class="o">=</span> <span class="n">read_user_input</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">cleaned_input</span> <span class="o">=</span> <span class="n">sanitize</span><span class="p">(</span><span class="n">raw_input</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">formatted_output</span> <span class="o">=</span> <span class="n">format_for_display</span><span class="p">(</span><span class="n">cleaned_input</span><span class="p">)</span></span></span></code></pre></div><h3 id="讀者應該能像讀故事一樣理解程式碼">讀者應該能像讀故事一樣理解程式碼</h3>
<p>如果讀者需要：</p>
<ul>
<li>往回翻看變數定義</li>
<li>查閱文件理解函式功能</li>
<li>猜測縮寫的含義</li>
</ul>
<p>那就不是好的故事。好的故事讓讀者自然地跟著情節走。</p>
<h2 id="變數命名的藝術">變數命名的藝術</h2>
<h3 id="壞名稱的特徵">壞名稱的特徵</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 壞：過於簡短，無法理解</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">t</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 壞：過於通用，無法區分</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">get_user_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">data2</span> <span class="o">=</span> <span class="n">get_order_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">temp</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">combine</span><span class="p">(</span><span class="n">temp</span><span class="p">,</span> <span class="n">data2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 壞：誤導性的名稱</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">user_list</span> <span class="o">=</span> <span class="n">get_user</span><span class="p">()</span>  <span class="c1"># 實際上回傳單一用戶，不是列表！</span></span></span></code></pre></div><h3 id="好名稱的特徵">好名稱的特徵</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 好：說明「這是什麼」</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">user_profile</span> <span class="o">=</span> <span class="n">get_user_profile</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">current_timestamp</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">validated_items</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">item_index</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 好：能區分不同用途</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">user_data</span> <span class="o">=</span> <span class="n">get_user_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">order_data</span> <span class="o">=</span> <span class="n">get_order_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">merged_report</span> <span class="o">=</span> <span class="n">merge_user_and_orders</span><span class="p">(</span><span class="n">user_data</span><span class="p">,</span> <span class="n">order_data</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="n">active_user</span> <span class="o">=</span> <span class="n">get_active_user</span><span class="p">()</span>  <span class="c1"># 單數名稱，回傳單一用戶</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">active_users</span> <span class="o">=</span> <span class="n">get_active_users</span><span class="p">()</span>  <span class="c1"># 複數名稱，回傳列表</span></span></span></code></pre></div><h3 id="命名原則說明這是什麼不是怎麼來的">命名原則：說明「這是什麼」，不是「怎麼來的」</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：名稱說明來源</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">config_from_yaml</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">user_after_validation</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="n">user</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"># 好：名稱說明內容</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">app_config</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">valid_user</span> <span class="o">=</span> <span class="n">validate</span><span class="p">(</span><span class="n">user</span><span class="p">)</span></span></span></code></pre></div><h3 id="布林變數的命名">布林變數的命名</h3>
<p>布林變數應該讀起來像一個問句的答案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：不清楚是什麼意思</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">user_status</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">file_check</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 好：讀起來像問句的答案</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">is_user_active</span> <span class="o">=</span> <span class="kc">True</span>      <span class="c1"># &#34;Is user active?&#34; - Yes</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">has_valid_license</span> <span class="o">=</span> <span class="kc">False</span>  <span class="c1"># &#34;Has valid license?&#34; - No</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">can_edit_document</span> <span class="o">=</span> <span class="kc">True</span>   <span class="c1"># &#34;Can edit document?&#34; - Yes</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="n">should_retry</span> <span class="o">=</span> <span class="kc">False</span>       <span class="c1"># &#34;Should retry?&#34; - No</span></span></span></code></pre></div><h3 id="集合類型的命名">集合類型的命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：不清楚是單一還是多個</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">get_all_users</span><span class="p">()</span>  <span class="c1"># 回傳列表，但名稱是單數</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">user_list</span> <span class="o">=</span> <span class="n">get_user</span><span class="p">()</span>  <span class="c1"># 回傳單一用戶，但名稱暗示列表</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 好：名稱反映結構</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">users</span> <span class="o">=</span> <span class="n">get_all_users</span><span class="p">()</span>           <span class="c1"># 複數 = 列表</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>          <span class="c1"># 單數 = 單一物件</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">user_ids</span> <span class="o">=</span> <span class="n">get_all_user_ids</span><span class="p">()</span>     <span class="c1"># 複數 + 型別提示</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="n">user_id_to_name</span> <span class="o">=</span> <span class="n">build_user_map</span><span class="p">()</span>  <span class="c1"># 說明映射關係</span></span></span></code></pre></div><h2 id="函式命名的藝術">函式命名的藝術</h2>
<h3 id="壞名稱的特徵-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"># 壞：動詞太模糊</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">do_something</span><span class="p">(</span><span class="n">item</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">manage_users</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 壞：不清楚會做什麼</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">user_operation</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">action</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">data_stuff</span><span class="p">(</span><span class="n">d</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="好名稱的特徵-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"># 好：清楚說明動作和目標</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">validate_user_input</span><span class="p">(</span><span class="n">user_input</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">extract_branch_name</span><span class="p">(</span><span class="n">git_output</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">format_error_message</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">calculate_total_price</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">OrderItem</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Decimal</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="命名原則說明做什麼不是怎麼做">命名原則：說明「做什麼」，不是「怎麼做」</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：名稱洩漏實作細節</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">loop_through_and_sum</span><span class="p">(</span><span class="n">numbers</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">use_regex_to_find_emails</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 好：名稱說明意圖</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">calculate_sum</span><span class="p">(</span><span class="n">numbers</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">extract_email_addresses</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="常見動詞模式">常見動詞模式</h3>
<table>
  <thead>
      <tr>
          <th>動詞</th>
          <th>使用場景</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>get</code></td>
          <td>取得現有的值</td>
          <td><code>get_user_name()</code></td>
      </tr>
      <tr>
          <td><code>set</code></td>
          <td>設定值</td>
          <td><code>set_user_name()</code></td>
      </tr>
      <tr>
          <td><code>create</code></td>
          <td>建立新物件</td>
          <td><code>create_user()</code></td>
      </tr>
      <tr>
          <td><code>build</code></td>
          <td>組裝複雜物件</td>
          <td><code>build_report()</code></td>
      </tr>
      <tr>
          <td><code>calculate</code></td>
          <td>計算數值</td>
          <td><code>calculate_total()</code></td>
      </tr>
      <tr>
          <td><code>validate</code></td>
          <td>驗證資料</td>
          <td><code>validate_input()</code></td>
      </tr>
      <tr>
          <td><code>parse</code></td>
          <td>解析文字</td>
          <td><code>parse_config()</code></td>
      </tr>
      <tr>
          <td><code>format</code></td>
          <td>格式化輸出</td>
          <td><code>format_date()</code></td>
      </tr>
      <tr>
          <td><code>convert</code></td>
          <td>轉換型別</td>
          <td><code>convert_to_json()</code></td>
      </tr>
      <tr>
          <td><code>extract</code></td>
          <td>從資料中提取</td>
          <td><code>extract_ids()</code></td>
      </tr>
      <tr>
          <td><code>filter</code></td>
          <td>過濾資料</td>
          <td><code>filter_active_users()</code></td>
      </tr>
      <tr>
          <td><code>find</code></td>
          <td>尋找符合條件的</td>
          <td><code>find_user_by_email()</code></td>
      </tr>
      <tr>
          <td><code>is/has/can</code></td>
          <td>布林判斷</td>
          <td><code>is_valid()</code>, <code>has_permission()</code></td>
      </tr>
  </tbody>
</table>
<h3 id="對稱命名">對稱命名</h3>
<p>相關的函式應該有對稱的命名：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 好：對稱的命名</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">open_connection</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">close_connection</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 好：對稱的命名</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">start_processing</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">stop_processing</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 不好：不對稱</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">def</span> <span class="nf">open_connection</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">disconnect</span><span class="p">():</span>  <span class="c1"># 應該是 close_connection</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="命名與認知負擔的關係">命名與認知負擔的關係</h2>
<h3 id="好的命名--讀者不需要記住前面發生什麼">好的命名 = 讀者不需要記住前面發生什麼</h3>
<p>比較這兩段程式碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 高認知負擔版本</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">fetch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">clean</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">transform</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="n">aggregate</span><span class="p">(</span><span class="n">d</span><span class="p">)</span></span></span></code></pre></div><p>讀到最後一行時，你需要記住：</p>
<ul>
<li><code>d</code> 一開始是什麼</li>
<li><code>d</code> 經過了哪些處理</li>
<li>現在的 <code>d</code> 是什麼狀態</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 低認知負擔版本</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">raw_data</span> <span class="o">=</span> <span class="n">fetch_user_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">cleaned_data</span> <span class="o">=</span> <span class="n">remove_invalid_entries</span><span class="p">(</span><span class="n">raw_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">normalized_data</span> <span class="o">=</span> <span class="n">normalize_formats</span><span class="p">(</span><span class="n">cleaned_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">report</span> <span class="o">=</span> <span class="n">generate_summary_report</span><span class="p">(</span><span class="n">normalized_data</span><span class="p">)</span></span></span></code></pre></div><p>讀到最後一行時，你只需要知道：</p>
<ul>
<li><code>normalized_data</code> 是正規化後的資料</li>
<li><code>generate_summary_report</code> 會產生報告</li>
</ul>
<p>你不需要記住前面的處理過程，因為名稱已經告訴你每個變數「是什麼」。</p>
<h3 id="認知負擔的量化分析">認知負擔的量化分析</h3>
<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"># 需要追溯 4 步</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">()</span>      <span class="c1"># 第 1 步</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>      <span class="c1"># 第 2 步</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">x</span> <span class="o">=</span> <span class="nb">filter</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>       <span class="c1"># 第 3 步</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="nb">format</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>  <span class="c1"># 第 4 步：x 到底是什麼？</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 不需要追溯</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">raw_data</span> <span class="o">=</span> <span class="n">get_data</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">processed_data</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">raw_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">filtered_data</span> <span class="o">=</span> <span class="nb">filter</span><span class="p">(</span><span class="n">processed_data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">formatted_output</span> <span class="o">=</span> <span class="nb">format</span><span class="p">(</span><span class="n">filtered_data</span><span class="p">)</span>  <span class="c1"># 直接看名稱就知道是過濾後的資料</span></span></span></code></pre></div><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="c1"># 重構前（高認知負擔）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">p</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">c</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">p</span><span class="p">)</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 class="k">if</span> <span class="n">c</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#!&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">l</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">l</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">return</span> <span class="n">l</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;# -*- coding&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="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"># 重構後（低認知負擔）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">has_valid_python_header</span><span class="p">(</span><span class="n">file_path</span><span class="p">:</span> <span class="n">Path</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">12</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查 Python 檔案是否有有效的檔頭&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">file_content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>
</span></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">has_shebang</span> <span class="o">=</span> <span class="n">file_content</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">has_shebang</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></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="n">file_content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">has_encoding_declaration</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">lines</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;# -*- coding&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="n">has_encoding_declaration</span></span></span></code></pre></div><h2 id="命名的自我檢查清單">命名的自我檢查清單</h2>
<p>撰寫程式碼時，對每個名稱問自己：</p>
<h3 id="變數命名">變數命名</h3>
<ul>
<li><input disabled="" type="checkbox"> 名稱是否說明「這是什麼」？</li>
<li><input disabled="" type="checkbox"> 讀者是否能在不看定義的情況下理解？</li>
<li><input disabled="" type="checkbox"> 布林變數是否以 is/has/can/should 開頭？</li>
<li><input disabled="" type="checkbox"> 集合是否使用複數形式？</li>
<li><input disabled="" type="checkbox"> 名稱是否和實際內容一致？</li>
</ul>
<h3 id="函式命名">函式命名</h3>
<ul>
<li><input disabled="" type="checkbox"> 名稱是否以動詞開頭？</li>
<li><input disabled="" type="checkbox"> 名稱是否說明「做什麼」而非「怎麼做」？</li>
<li><input disabled="" type="checkbox"> 相關函式是否有對稱的命名？</li>
<li><input disabled="" type="checkbox"> 讀者是否能從名稱推測回傳值？</li>
<li><input disabled="" type="checkbox"> 名稱是否符合常見的動詞模式？</li>
</ul>
<h3 id="整體檢查">整體檢查</h3>
<ul>
<li><input disabled="" type="checkbox"> 讀者是否能像讀故事一樣閱讀程式碼？</li>
<li><input disabled="" type="checkbox"> 讀者是否需要往回追溯才能理解？</li>
<li><input disabled="" type="checkbox"> 名稱是否有歧義或誤導性？</li>
</ul>
<h2 id="小結">小結</h2>
<p>命名是降低認知負擔最直接的方法。好的命名讓程式碼自己說話，不需要註解、不需要追溯、不需要猜測。</p>
<p>記住這個原則：</p>
<blockquote>
<p>如果你需要寫註解來解釋一個變數或函式，那可能是名稱不夠好。</p></blockquote>
<p>讓程式碼說故事，讓讀者輕鬆理解。這就是命名的藝術。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的</a> - 理解命名為何重要</li>
<li><a href="/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔</a> - 命名在架構設計中的角色</li>
</ul>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>Martin, R. C. (2008). &ldquo;Clean Code&rdquo; - Chapter 2: Meaningful Names</li>
<li>Boswell, D. &amp; Foucher, T. (2011). &ldquo;The Art of Readable Code&rdquo;</li>
</ul>
]]></content:encoded></item><item><title>0.3 錯誤處理：把失敗路徑寫出來</title><link>https://tarrragon.github.io/blog/go/00-philosophy/error-thinking/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/error-thinking/</guid><description>&lt;p>Go 錯誤處理的核心原則是把失敗路徑明確寫在程式流程中。&lt;code>if err != nil&lt;/code> 看起來重複，但它讓每一步可能失敗的地方都可見，也讓讀者能直接知道失敗時程式會怎麼結束。對需要長時間運行、並發處理、背景工作或即時請求回應的服務來說，這種顯式失敗路徑比隱式例外更容易維護。&lt;/p>
&lt;h2 id="為什麼這章在第零章">為什麼這章在第零章&lt;/h2>
&lt;p>如果你的場景已經把 Go 推向服務型系統，錯誤處理就是維持服務穩定的核心能力。這一章要先建立的是：Go 會要求你把失敗說清楚，因為在高併發或長時間運行的情境下，模糊的失敗行為會比清楚的錯誤訊息更難排查。&lt;/p>
&lt;h2 id="error-是普通回傳值">error 是普通回傳值&lt;/h2>
&lt;p>Go 的 &lt;code>error&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">LoadConfig&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"> 2&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="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadFile&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"> 3&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"> 4&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">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;read config %q: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">path&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"> 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">var&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="nx">Config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmarshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&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="kc">nil&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="nx">Config&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;parse config %q: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">path&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">10&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">config&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">13&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式有兩個失敗點：讀檔失敗與 JSON 解析失敗。每個失敗點都立刻處理並回傳，正常流程則留在函式底部。&lt;/p>
&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">CreateUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">email&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">User&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">email&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">email&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">email&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="nx">User&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">email&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="nx">User&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">User&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">email&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;h2 id="包裝錯誤要補上操作脈絡">包裝錯誤要補上操作脈絡&lt;/h2>
&lt;p>錯誤包裝的核心責任是保留原始錯誤，同時補上當前操作脈絡。&lt;code>fmt.Errorf&lt;/code> 搭配 &lt;code>%w&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">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">repository&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&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">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;save user %q: %w&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Email&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">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>save user &amp;quot;alice@example.com&amp;quot;&lt;/code> 是當前操作脈絡，原始錯誤則被 &lt;code>%w&lt;/code> 保留下來。呼叫端可以印出完整錯誤，也可以用 &lt;code>errors.Is&lt;/code> 或 &lt;code>errors.As&lt;/code> 檢查特定錯誤。&lt;/p>
&lt;p>錯誤包裝應提供新的操作脈絡。&lt;code>fmt.Errorf(&amp;quot;failed: %w&amp;quot;, err)&lt;/code> 這類包裝沒有資訊量；好的錯誤訊息應該回答「做什麼失敗」與「關鍵資料是什麼」。&lt;/p>
&lt;h2 id="http-handler-要把錯誤轉成協定語意">HTTP handler 要把錯誤轉成協定語意&lt;/h2>
&lt;p>HTTP handler 的錯誤處理核心是把內部錯誤轉成明確 status code。輸入格式錯誤通常是 &lt;code>400&lt;/code>，找不到資料是 &lt;code>404&lt;/code>，不支援的方法是 &lt;code>405&lt;/code>，未預期內部錯誤才是 &lt;code>500&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">handleCreateUser&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"> 2&lt;/span>&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="nx">createUserRequest&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">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewDecoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Body&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">req&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&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;invalid json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&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>&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="nx">user&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">CreateUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Email&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="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">10&lt;/span>&lt;span class="cl"> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusBadRequest&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>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="nf">writeJSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusCreated&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>handler 的錯誤對應應反映協定語意。錯誤來自呼叫端輸入時，回 &lt;code>400&lt;/code> 才能讓 client 知道應該修正 request；未預期內部錯誤才應進入 &lt;code>500&lt;/code>。&lt;/p>
&lt;h2 id="log-應該放在有處理責任的位置">log 應該放在有處理責任的位置&lt;/h2>
&lt;p>錯誤記錄的核心規則是誰負責處理錯誤，誰才記錄錯誤。底層函式通常回傳錯誤，上層邊界再決定要 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log&lt;/a>、重試、轉成 HTTP response 或讓程式結束。&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">run&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"> 2&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">LoadConfig&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"> 3&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"> 4&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"> 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">return&lt;/span> &lt;span class="nf">StartServer&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="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="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">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="nf">run&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="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&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;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>LoadConfig&lt;/code> 回傳錯誤比直接 &lt;code>log.Fatal&lt;/code> 更符合責任邊界，因為它不知道呼叫端是否想重試、使用預設值或結束程式。&lt;code>main&lt;/code> 是 process 邊界，才適合決定失敗時結束。&lt;/p>
&lt;h2 id="小結">小結&lt;/h2>
&lt;p>Go 錯誤處理的價值是讓失敗路徑可讀、可測、可追蹤。每個 &lt;code>if err != nil&lt;/code> 都是一個明確的決策點：是否補脈絡、是否轉成協定狀態、是否記錄、是否終止流程。這種顯式設計是 Go 長期維護性的核心之一。&lt;/p></description><content:encoded><![CDATA[<p>Go 錯誤處理的核心原則是把失敗路徑明確寫在程式流程中。<code>if err != nil</code> 看起來重複，但它讓每一步可能失敗的地方都可見，也讓讀者能直接知道失敗時程式會怎麼結束。對需要長時間運行、並發處理、背景工作或即時請求回應的服務來說，這種顯式失敗路徑比隱式例外更容易維護。</p>
<h2 id="為什麼這章在第零章">為什麼這章在第零章</h2>
<p>如果你的場景已經把 Go 推向服務型系統，錯誤處理就是維持服務穩定的核心能力。這一章要先建立的是：Go 會要求你把失敗說清楚，因為在高併發或長時間運行的情境下，模糊的失敗行為會比清楚的錯誤訊息更難排查。</p>
<h2 id="error-是普通回傳值">error 是普通回傳值</h2>
<p>Go 的 <code>error</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">LoadConfig</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"> 2</span><span class="cl"> <span class="nx">data</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="nx">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="nx">err</span> <span class="o">!=</span> <span class="kc">nil</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="nx">Config</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;read config %q: %w&#34;</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">err</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="kd">var</span> <span class="nx">config</span> <span class="nx">Config</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">config</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"> 9</span><span class="cl">     <span class="k">return</span> <span class="nx">Config</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;parse config %q: %w&#34;</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這段程式有兩個失敗點：讀檔失敗與 JSON 解析失敗。每個失敗點都立刻處理並回傳，正常流程則留在函式底部。</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="kd">func</span> <span class="nf">CreateUser</span><span class="p">(</span><span class="nx">email</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">User</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">email</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">email</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">email</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="nx">User</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">email</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="nx">User</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">User</span><span class="p">{</span><span class="nx">Email</span><span class="p">:</span> <span class="nx">email</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>
<h2 id="包裝錯誤要補上操作脈絡">包裝錯誤要補上操作脈絡</h2>
<p>錯誤包裝的核心責任是保留原始錯誤，同時補上當前操作脈絡。<code>fmt.Errorf</code> 搭配 <code>%w</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">err</span> <span class="o">:=</span> <span class="nx">repository</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">user</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">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;save user %q: %w&#34;</span><span class="p">,</span> <span class="nx">user</span><span class="p">.</span><span class="nx">Email</span><span class="p">,</span> <span class="nx">err</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>save user &quot;alice@example.com&quot;</code> 是當前操作脈絡，原始錯誤則被 <code>%w</code> 保留下來。呼叫端可以印出完整錯誤，也可以用 <code>errors.Is</code> 或 <code>errors.As</code> 檢查特定錯誤。</p>
<p>錯誤包裝應提供新的操作脈絡。<code>fmt.Errorf(&quot;failed: %w&quot;, err)</code> 這類包裝沒有資訊量；好的錯誤訊息應該回答「做什麼失敗」與「關鍵資料是什麼」。</p>
<h2 id="http-handler-要把錯誤轉成協定語意">HTTP handler 要把錯誤轉成協定語意</h2>
<p>HTTP handler 的錯誤處理核心是把內部錯誤轉成明確 status code。輸入格式錯誤通常是 <code>400</code>，找不到資料是 <code>404</code>，不支援的方法是 <code>405</code>，未預期內部錯誤才是 <code>500</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">handleCreateUser</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"> 2</span><span class="cl"> <span class="kd">var</span> <span class="nx">req</span> <span class="nx">createUserRequest</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nf">NewDecoder</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">Body</span><span class="p">).</span><span class="nf">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">req</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"> 4</span><span class="cl">  <span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;invalid json&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">return</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="nx">user</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">CreateUser</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">Email</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">(),</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">return</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="nf">writeJSON</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusCreated</span><span class="p">,</span> <span class="nx">user</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>handler 的錯誤對應應反映協定語意。錯誤來自呼叫端輸入時，回 <code>400</code> 才能讓 client 知道應該修正 request；未預期內部錯誤才應進入 <code>500</code>。</p>
<h2 id="log-應該放在有處理責任的位置">log 應該放在有處理責任的位置</h2>
<p>錯誤記錄的核心規則是誰負責處理錯誤，誰才記錄錯誤。底層函式通常回傳錯誤，上層邊界再決定要 <a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a>、重試、轉成 HTTP response 或讓程式結束。</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">run</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</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">LoadConfig</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"> 3</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"> 4</span><span class="cl">  <span class="k">return</span> <span class="nx">err</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">return</span> <span class="nf">StartServer</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="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">main</span><span class="p">()</span> <span class="p">{</span>
</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="nf">run</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="nx">log</span><span class="p">.</span><span class="nf">Fatal</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></code></pre></div><p><code>LoadConfig</code> 回傳錯誤比直接 <code>log.Fatal</code> 更符合責任邊界，因為它不知道呼叫端是否想重試、使用預設值或結束程式。<code>main</code> 是 process 邊界，才適合決定失敗時結束。</p>
<h2 id="小結">小結</h2>
<p>Go 錯誤處理的價值是讓失敗路徑可讀、可測、可追蹤。每個 <code>if err != nil</code> 都是一個明確的決策點：是否補脈絡、是否轉成協定狀態、是否記錄、是否終止流程。這種顯式設計是 Go 長期維護性的核心之一。</p>
]]></content:encoded></item><item><title>開放封閉原則與認知負擔</title><link>https://tarrragon.github.io/blog/python/00-philosophy/open-closed-principle/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/open-closed-principle/</guid><description>&lt;h2 id="ocp-的傳統解釋">OCP 的傳統解釋&lt;/h2>
&lt;p>開放封閉原則（Open-Closed Principle, OCP）是 SOLID 原則之一，傳統定義是：&lt;/p>
&lt;blockquote>
&lt;p>軟體實體（類別、模組、函式）應該對擴展開放，對修改封閉。&lt;/p>&lt;/blockquote>
&lt;p>這意味著：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>對擴展開放&lt;/strong>：可以增加新功能&lt;/li>
&lt;li>&lt;strong>對修改封閉&lt;/strong>：不需要修改現有程式碼&lt;/li>
&lt;/ul>
&lt;h3 id="傳統焦點避免修改帶來的風險">傳統焦點：避免修改帶來的風險&lt;/h3>
&lt;p>傳統觀點認為，OCP 的目的是：&lt;/p>
&lt;ul>
&lt;li>避免修改穩定的程式碼引入錯誤&lt;/li>
&lt;li>減少回歸測試的範圍&lt;/li>
&lt;li>保護現有功能不受影響&lt;/li>
&lt;/ul>
&lt;p>這些都是正確的，但還有一個更深層的目的。&lt;/p>
&lt;h2 id="ocp-的認知負擔視角用戶觀點">OCP 的認知負擔視角（用戶觀點）&lt;/h2>
&lt;h3 id="真正目的讓閱讀者不需要理解整個系統才能使用">真正目的：讓閱讀者不需要理解整個系統才能使用&lt;/h3>
&lt;p>從認知負擔的角度來看，OCP 的核心價值是：&lt;/p>
&lt;blockquote>
&lt;p>擴展系統時，開發者只需要理解介面，不需要理解實作。&lt;/p>&lt;/blockquote>
&lt;p>這大幅降低了認知負擔。&lt;/p>
&lt;h3 id="範例違反-ocp-的設計">範例：違反 OCP 的設計&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="k">class&lt;/span> &lt;span class="nc">ReportGenerator&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">def&lt;/span> &lt;span class="nf">generate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">report_type&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">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">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="k">if&lt;/span> &lt;span class="n">report_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;pdf&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="c1"># 100 行 PDF 生成邏輯&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_generate_pdf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">report_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;excel&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="c1"># 80 行 Excel 生成邏輯&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_generate_excel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">report_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;html&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="c1"># 60 行 HTML 生成邏輯&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_generate_html&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">else&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">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Unknown report type: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">report_type&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：&lt;/p>
&lt;ul>
&lt;li>新增格式需要修改這個類別&lt;/li>
&lt;li>理解新增功能需要閱讀整個類別&lt;/li>
&lt;li>每種格式的邏輯混在一起&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>認知負擔&lt;/strong>：要新增 CSV 格式，開發者需要理解整個 &lt;code>ReportGenerator&lt;/code> 類別的結構，找到正確的位置插入程式碼，並確保不影響其他格式。&lt;/p>
&lt;h3 id="範例遵循-ocp-的設計">範例：遵循 OCP 的設計&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">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ABC&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ReportFormatter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ABC&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;報告格式化器的抽象介面&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;將資料格式化為報告&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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">class&lt;/span> &lt;span class="nc">PdfFormatter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ReportFormatter&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># PDF 生成邏輯&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ExcelFormatter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ReportFormatter&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">def&lt;/span> &lt;span class="nf">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&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="c1"># Excel 生成邏輯&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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HtmlFormatter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ReportFormatter&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># HTML 生成邏輯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ReportGenerator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">formatter&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ReportFormatter&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_formatter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">formatter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">generate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_formatter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>新增 CSV 格式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CsvFormatter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ReportFormatter&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">def&lt;/span> &lt;span class="nf">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">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="c1"># CSV 生成邏輯&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">pass&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">generator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ReportGenerator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CsvFormatter&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">report&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generator&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>認知負擔&lt;/strong>：要新增 CSV 格式，開發者只需要：&lt;/p>
&lt;ol>
&lt;li>理解 &lt;code>ReportFormatter&lt;/code> 介面（一個方法）&lt;/li>
&lt;li>實作 &lt;code>format&lt;/code> 方法&lt;/li>
&lt;/ol>
&lt;p>不需要閱讀 &lt;code>PdfFormatter&lt;/code>、&lt;code>ExcelFormatter&lt;/code> 或 &lt;code>ReportGenerator&lt;/code> 的實作。&lt;/p>
&lt;h3 id="擴展時只需要理解介面不需要理解實作">擴展時只需要理解介面，不需要理解實作&lt;/h3>
&lt;p>這就是 OCP 降低認知負擔的方式：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情境&lt;/th>
 &lt;th>違反 OCP&lt;/th>
 &lt;th>遵循 OCP&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>新增格式&lt;/td>
 &lt;td>需要理解整個類別&lt;/td>
 &lt;td>只需理解介面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修改一個格式&lt;/td>
 &lt;td>可能影響其他格式&lt;/td>
 &lt;td>完全隔離&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>閱讀程式碼&lt;/td>
 &lt;td>需要跟蹤 if-else 分支&lt;/td>
 &lt;td>直接看對應的類別&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="這和命名是同一件事降低認知負擔">這和命名是同一件事：降低認知負擔&lt;/h3>
&lt;p>回想&lt;a href="https://tarrragon.github.io/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術&lt;/a>：好的命名讓讀者不需要追溯定義就能理解。&lt;/p>
&lt;p>OCP 做的是同樣的事，只是在更高的層級：好的設計讓讀者不需要理解整個系統就能擴展。&lt;/p>
&lt;h2 id="單一職責原則的本質">單一職責原則的本質&lt;/h2>
&lt;p>單一職責原則（Single Responsibility Principle, SRP）是另一個 SOLID 原則：&lt;/p>
&lt;blockquote>
&lt;p>一個類別應該只有一個改變的理由。&lt;/p>&lt;/blockquote>
&lt;h3 id="一次只理解一件事">一次只理解一件事&lt;/h3>
&lt;p>從認知負擔的角度，SRP 的核心是：&lt;/p>
&lt;blockquote>
&lt;p>讓讀者一次只需要理解一件事。&lt;/p>&lt;/blockquote>





&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"># 違反 SRP：一個類別做太多事&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">class&lt;/span> &lt;span class="nc">UserManager&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">def&lt;/span> &lt;span class="nf">create_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證邏輯&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="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="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">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">delete_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">user_id&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 權限檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 資料庫操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 清理關聯資料&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 發送通知&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 記錄日誌&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">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：讀者想理解「如何發送歡迎郵件」，卻需要閱讀整個 &lt;code>UserManager&lt;/code> 類別。&lt;/p></description><content:encoded><![CDATA[<h2 id="ocp-的傳統解釋">OCP 的傳統解釋</h2>
<p>開放封閉原則（Open-Closed Principle, OCP）是 SOLID 原則之一，傳統定義是：</p>
<blockquote>
<p>軟體實體（類別、模組、函式）應該對擴展開放，對修改封閉。</p></blockquote>
<p>這意味著：</p>
<ul>
<li><strong>對擴展開放</strong>：可以增加新功能</li>
<li><strong>對修改封閉</strong>：不需要修改現有程式碼</li>
</ul>
<h3 id="傳統焦點避免修改帶來的風險">傳統焦點：避免修改帶來的風險</h3>
<p>傳統觀點認為，OCP 的目的是：</p>
<ul>
<li>避免修改穩定的程式碼引入錯誤</li>
<li>減少回歸測試的範圍</li>
<li>保護現有功能不受影響</li>
</ul>
<p>這些都是正確的，但還有一個更深層的目的。</p>
<h2 id="ocp-的認知負擔視角用戶觀點">OCP 的認知負擔視角（用戶觀點）</h2>
<h3 id="真正目的讓閱讀者不需要理解整個系統才能使用">真正目的：讓閱讀者不需要理解整個系統才能使用</h3>
<p>從認知負擔的角度來看，OCP 的核心價值是：</p>
<blockquote>
<p>擴展系統時，開發者只需要理解介面，不需要理解實作。</p></blockquote>
<p>這大幅降低了認知負擔。</p>
<h3 id="範例違反-ocp-的設計">範例：違反 OCP 的設計</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">ReportGenerator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">def</span> <span class="nf">generate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">report_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</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">report_type</span> <span class="o">==</span> <span class="s2">&#34;pdf&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">            <span class="c1"># 100 行 PDF 生成邏輯</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_pdf</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">elif</span> <span class="n">report_type</span> <span class="o">==</span> <span class="s2">&#34;excel&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="c1"># 80 行 Excel 生成邏輯</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_excel</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">elif</span> <span class="n">report_type</span> <span class="o">==</span> <span class="s2">&#34;html&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="c1"># 60 行 HTML 生成邏輯</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_html</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown report type: </span><span class="si">{</span><span class="n">report_type</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p>問題：</p>
<ul>
<li>新增格式需要修改這個類別</li>
<li>理解新增功能需要閱讀整個類別</li>
<li>每種格式的邏輯混在一起</li>
</ul>
<p><strong>認知負擔</strong>：要新增 CSV 格式，開發者需要理解整個 <code>ReportGenerator</code> 類別的結構，找到正確的位置插入程式碼，並確保不影響其他格式。</p>
<h3 id="範例遵循-ocp-的設計">範例：遵循 OCP 的設計</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">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ReportFormatter</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;報告格式化器的抽象介面&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;&#34;&#34;將資料格式化為報告&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">class</span> <span class="nc">PdfFormatter</span><span class="p">(</span><span class="n">ReportFormatter</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="c1"># PDF 生成邏輯</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">class</span> <span class="nc">ExcelFormatter</span><span class="p">(</span><span class="n">ReportFormatter</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># Excel 生成邏輯</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">HtmlFormatter</span><span class="p">(</span><span class="n">ReportFormatter</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># HTML 生成邏輯</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">class</span> <span class="nc">ReportGenerator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">formatter</span><span class="p">:</span> <span class="n">ReportFormatter</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_formatter</span> <span class="o">=</span> <span class="n">formatter</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">generate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_formatter</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div><p>新增 CSV 格式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">class</span> <span class="nc">CsvFormatter</span><span class="p">(</span><span class="n">ReportFormatter</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="c1"># CSV 生成邏輯</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">generator</span> <span class="o">=</span> <span class="n">ReportGenerator</span><span class="p">(</span><span class="n">CsvFormatter</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">report</span> <span class="o">=</span> <span class="n">generator</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div><p><strong>認知負擔</strong>：要新增 CSV 格式，開發者只需要：</p>
<ol>
<li>理解 <code>ReportFormatter</code> 介面（一個方法）</li>
<li>實作 <code>format</code> 方法</li>
</ol>
<p>不需要閱讀 <code>PdfFormatter</code>、<code>ExcelFormatter</code> 或 <code>ReportGenerator</code> 的實作。</p>
<h3 id="擴展時只需要理解介面不需要理解實作">擴展時只需要理解介面，不需要理解實作</h3>
<p>這就是 OCP 降低認知負擔的方式：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>違反 OCP</th>
          <th>遵循 OCP</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新增格式</td>
          <td>需要理解整個類別</td>
          <td>只需理解介面</td>
      </tr>
      <tr>
          <td>修改一個格式</td>
          <td>可能影響其他格式</td>
          <td>完全隔離</td>
      </tr>
      <tr>
          <td>閱讀程式碼</td>
          <td>需要跟蹤 if-else 分支</td>
          <td>直接看對應的類別</td>
      </tr>
  </tbody>
</table>
<h3 id="這和命名是同一件事降低認知負擔">這和命名是同一件事：降低認知負擔</h3>
<p>回想<a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術</a>：好的命名讓讀者不需要追溯定義就能理解。</p>
<p>OCP 做的是同樣的事，只是在更高的層級：好的設計讓讀者不需要理解整個系統就能擴展。</p>
<h2 id="單一職責原則的本質">單一職責原則的本質</h2>
<p>單一職責原則（Single Responsibility Principle, SRP）是另一個 SOLID 原則：</p>
<blockquote>
<p>一個類別應該只有一個改變的理由。</p></blockquote>
<h3 id="一次只理解一件事">一次只理解一件事</h3>
<p>從認知負擔的角度，SRP 的核心是：</p>
<blockquote>
<p>讓讀者一次只需要理解一件事。</p></blockquote>





<div class="highlight"><pre 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"># 違反 SRP：一個類別做太多事</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">UserManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">create_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="c1"># 驗證邏輯</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1"># 資料庫操作</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 發送歡迎郵件</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="c1"># 記錄日誌</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">delete_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 權限檢查</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 資料庫操作</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="c1"># 清理關聯資料</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="c1"># 發送通知</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 記錄日誌</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>問題：讀者想理解「如何發送歡迎郵件」，卻需要閱讀整個 <code>UserManager</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"># 遵循 SRP：每個類別只做一件事</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">UserValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">UserRepository</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">create</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user</span><span class="p">:</span> <span class="n">User</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</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">11</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">EmailService</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">send_welcome_email</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user</span><span class="p">:</span> <span class="n">User</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">class</span> <span class="nc">UserService</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">UserValidator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">repository</span><span class="p">:</span> <span class="n">UserRepository</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">email_service</span><span class="p">:</span> <span class="n">EmailService</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_repository</span> <span class="o">=</span> <span class="n">repository</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_email_service</span> <span class="o">=</span> <span class="n">email_service</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">create_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">validation</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">data</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">validation</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="n">validation</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">user</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_repository</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_email_service</span><span class="o">.</span><span class="n">send_welcome_email</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">user</span></span></span></code></pre></div><p>現在，讀者想理解「如何發送歡迎郵件」，只需要看 <code>EmailService</code>。</p>
<h3 id="類別函式的職責清晰--閱讀時認知負擔低">類別/函式的職責清晰 = 閱讀時認知負擔低</h3>
<table>
  <thead>
      <tr>
          <th>職責數量</th>
          <th>認知負擔</th>
          <th>維護難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>低</td>
          <td>容易</td>
      </tr>
      <tr>
          <td>2-3</td>
          <td>中</td>
          <td>需要注意</td>
      </tr>
      <tr>
          <td>4+</td>
          <td>高</td>
          <td>危險區域</td>
      </tr>
  </tbody>
</table>
<h3 id="和命名的關聯如果難以命名可能職責不單一">和命名的關聯：如果難以命名，可能職責不單一</h3>
<p>這是一個非常實用的檢測方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 難以命名 = 職責不單一</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">UserStuffManager</span><span class="p">:</span>  <span class="c1"># &#34;stuff&#34; 說明不知道它具體做什麼</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">DataProcessorAndValidator</span><span class="p">:</span>  <span class="c1"># &#34;and&#34; 說明做了兩件事</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">HelperUtils</span><span class="p">:</span>  <span class="c1"># &#34;helper/utils&#34; 說明是雜項收集</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 容易命名 = 職責單一</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">UserAuthenticator</span><span class="p">:</span>  <span class="c1"># 清楚：處理用戶認證</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigurationLoader</span><span class="p">:</span>  <span class="c1"># 清楚：載入配置</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">class</span> <span class="nc">EmailFormatter</span><span class="p">:</span>  <span class="c1"># 清楚：格式化郵件</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><p><strong>規則</strong>：如果你無法用一個簡短的名詞描述類別的職責，它可能做了太多事。</p>
<h2 id="實際案例">實際案例</h2>
<h3 id="hook-系統中的設計決策">Hook 系統中的設計決策</h3>
<p>讓我們看 Hook 系統中如何應用這些原則：</p>
<h4 id="配置載入器遵循-srp">配置載入器（遵循 SRP）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/lib/config_loader.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigLoader</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    單一職責：載入和快取配置檔案
</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">    - 驗證配置內容
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 使用配置執行操作
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 修改配置
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_path</span><span class="p">:</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="bp">self</span><span class="o">.</span><span class="n">_config_path</span> <span class="o">=</span> <span class="n">config_path</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">load</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入配置，使用快取避免重複讀取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_read_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</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">_read_config</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_config_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span></span></span></code></pre></div><h4 id="解析器工廠遵循-ocp">解析器工廠（遵循 OCP）</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/lib/parsers/base.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">LanguageParser</span><span class="p">(</span><span class="n">ABC</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></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ParseResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">get_supported_extensions</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># .claude/lib/parsers/python_parser.py</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">PythonParser</span><span class="p">(</span><span class="n">LanguageParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ParseResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># Python 解析邏輯</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">get_supported_extensions</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="s2">&#34;.py&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># .claude/lib/parsers/factory.py</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">class</span> <span class="nc">ParserFactory</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    對擴展開放：新增語言只需實作 LanguageParser
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    對修改封閉：不需要修改此工廠類別
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">_parsers</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">type</span><span class="p">[</span><span class="n">LanguageParser</span><span class="p">]]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">parser_class</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">LanguageParser</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">for</span> <span class="n">ext</span> <span class="ow">in</span> <span class="n">parser_class</span><span class="o">.</span><span class="n">get_supported_extensions</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="bp">cls</span><span class="o">.</span><span class="n">_parsers</span><span class="p">[</span><span class="n">ext</span><span class="p">]</span> <span class="o">=</span> <span class="n">parser_class</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">def</span> <span class="nf">get_parser</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">file_extension</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LanguageParser</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">parser_class</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_parsers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">file_extension</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">if</span> <span class="n">parser_class</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">raise</span> <span class="n">UnsupportedLanguageError</span><span class="p">(</span><span class="n">file_extension</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">return</span> <span class="n">parser_class</span><span class="p">()</span></span></span></code></pre></div><p>新增 Dart 語言支援：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/lib/parsers/dart_parser.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">DartParser</span><span class="p">(</span><span class="n">LanguageParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ParseResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="c1"># Dart 解析邏輯</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">get_supported_extensions</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</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="p">[</span><span class="s2">&#34;.dart&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 註冊（可在初始化時自動完成）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">ParserFactory</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">DartParser</span><span class="p">)</span></span></span></code></pre></div><p>開發者只需要：</p>
<ol>
<li>理解 <code>LanguageParser</code> 介面</li>
<li>實作兩個方法</li>
<li>註冊解析器</li>
</ol>
<p>不需要閱讀其他解析器的實作。</p>
<h3 id="如何用降低認知負擔來判斷設計好壞">如何用「降低認知負擔」來判斷設計好壞</h3>
<p>面對設計決策時，問自己這些問題：</p>
<ol>
<li>
<p><strong>擴展時</strong>：開發者需要理解多少現有程式碼？</p>
<ul>
<li>好：只需要理解介面</li>
<li>壞：需要理解整個實作</li>
</ul>
</li>
<li>
<p><strong>修改時</strong>：改動會影響多少其他部分？</p>
<ul>
<li>好：改動是局部的</li>
<li>壞：改動會連鎖反應</li>
</ul>
</li>
<li>
<p><strong>閱讀時</strong>：讀者一次需要記住多少概念？</p>
<ul>
<li>好：一次一個概念</li>
<li>壞：需要同時記住多個概念</li>
</ul>
</li>
</ol>
<h2 id="設計原則檢查清單">設計原則檢查清單</h2>
<h3 id="開放封閉原則">開放封閉原則</h3>
<ul>
<li><input disabled="" type="checkbox"> 新增功能是否不需要修改現有程式碼？</li>
<li><input disabled="" type="checkbox"> 擴展時是否只需要理解介面？</li>
<li><input disabled="" type="checkbox"> 是否有抽象層隔離變化點？</li>
</ul>
<h3 id="單一職責原則">單一職責原則</h3>
<ul>
<li><input disabled="" type="checkbox"> 類別是否只有一個改變的理由？</li>
<li><input disabled="" type="checkbox"> 類別名稱是否能清楚描述其職責？</li>
<li><input disabled="" type="checkbox"> 類別是否小到可以快速理解？</li>
</ul>
<h3 id="認知負擔檢查">認知負擔檢查</h3>
<ul>
<li><input disabled="" type="checkbox"> 讀者是否能在 5 分鐘內理解這個類別？</li>
<li><input disabled="" type="checkbox"> 是否需要閱讀其他類別才能理解這個類別？</li>
<li><input disabled="" type="checkbox"> 修改這個類別是否需要擔心影響其他部分？</li>
</ul>
<h2 id="小結">小結</h2>
<p>從認知負擔的視角來看，OCP 和 SRP 的核心目的是：</p>
<ul>
<li><strong>OCP</strong>：讓開發者不需要理解整個系統就能擴展</li>
<li><strong>SRP</strong>：讓開發者一次只需要理解一件事</li>
</ul>
<p>這和命名是同一個哲學：<strong>降低閱讀者的認知負擔</strong>。</p>
<p>當你面對設計決策時，不要問「這是否符合 OCP」，而是問：</p>
<blockquote>
<p>下一個開發者要擴展這個功能時，需要理解多少現有程式碼？</p></blockquote>
<p>答案越少，設計越好。</p>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的</a> - 認知負擔的基本概念</li>
<li><a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事</a> - 降低認知負擔的另一種方式</li>
<li><a href="/blog/python/04-oop/abc/" data-link-title="4.2 抽象基類 ABC" data-link-desc="定義介面契約">抽象基類 ABC</a> - Python 中實現 OCP 的工具</li>
</ul>
<h2 id="延伸閱讀進階系列">延伸閱讀（進階系列）</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">進階設計模式</a> - OCP 在複雜系統中的應用</li>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">插件系統設計</a> - 用 OCP 原則建構可擴展架構</li>
</ul>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>Martin, R. C. (2000). &ldquo;Design Principles and Design Patterns&rdquo;</li>
<li>Martin, R. C. (2017). &ldquo;Clean Architecture&rdquo;</li>
<li>Meyer, B. (1988). &ldquo;Object-Oriented Software Construction&rdquo;</li>
</ul>
]]></content:encoded></item><item><title>0.4 什麼時候選 Go</title><link>https://tarrragon.github.io/blog/go/00-philosophy/selecting-go/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/selecting-go/</guid><description>&lt;p>選擇 Go 的核心判斷是工作場景是否需要長時間運行、明確邊界、穩定併發與簡單部署。這一章用工程條件判斷 Go 是否適合目前問題；若工作更依賴框架模板、快速表單 CRUD、動態行為或大量 runtime magic，其他語言或框架可能更符合需求。&lt;/p>
&lt;p>選型文章的目標是建立判斷路徑。讀者未來面對的可能是 API service、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> server、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> worker、內部工具或資料處理流程；同一個語言在不同工作負載下會有不同價值。先理解判斷條件，再進入語法細節，才不會把「我會寫 Go」誤解成「所有問題都該用 Go」。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>判斷哪些工作負載適合 Go&lt;/li>
&lt;li>看出哪些系統型態特別適合 Go&lt;/li>
&lt;li>區分 Go 的強項與不適合硬上的場景&lt;/li>
&lt;li>用工程條件取代語言偏好來做選型&lt;/li>
&lt;li>為後續語法與實作章節建立正確的閱讀順序&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察先看工作負載再看語言">【觀察】先看工作負載，再看語言&lt;/h2>
&lt;p>Go 最常被選中的場景，是需要穩定處理大量服務型工作的系統。這些工作通常包含等待外部 I/O、管理長生命週期、持續處理背景任務或維持清楚服務邊界：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工作型態&lt;/th>
 &lt;th>為什麼適合 Go&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>高併發 I/O&lt;/td>
 &lt;td>goroutine 成本低，適合大量等待型工作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>長連線服務&lt;/td>
 &lt;td>容易管理生命週期、取消與資源清理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>背景 worker&lt;/td>
 &lt;td>可以把工作拆成小單位並持續處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件處理&lt;/td>
 &lt;td>channel、select 與明確邊界很適合事件流&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API service&lt;/td>
 &lt;td>標準庫直接支撐 HTTP、context、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>如果工作本質就是一堆等待外部 I/O 的操作，Go 往往比把整個問題放進單線程迴圈更自然。&lt;/p>
&lt;h3 id="高併發-io先看服務是否長時間等待外部回應">高併發 I/O：先看服務是否長時間等待外部回應&lt;/h3>
&lt;p>高併發 I/O 的核心特徵是「同時有很多工作在等待網路、檔案、資料庫或外部 API」。判斷時可以先看 request 的時間花在哪裡：如果大部分時間都在等下游回應，CPU 計算只占一小段，這就是等待型工作。&lt;/p>
&lt;p>接近真實網路服務的例子包括：&lt;/p>
&lt;ul>
&lt;li>API gateway 同時轉送請求到多個下游服務&lt;/li>
&lt;li>價格比較網站同時查詢多個供應商 API&lt;/li>
&lt;li>檔案上傳服務同時處理大量 client 的上傳進度&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook&lt;/a> receiver 同時接收付款、物流、通知平台的 callback&lt;/li>
&lt;/ul>
&lt;p>這類服務的主要工程問題是「同時有很多等待中的工作，而且每個工作都需要容量邊界」。Go 的 goroutine 可以讓每個等待中的 request 有清楚的執行單位，&lt;code>context&lt;/code> 可以控制 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 與取消，channel 或 semaphore 可以限制同時打到下游的數量。Go 的價值在於讓等待、取消與容量控制都變成明確程式結構。&lt;/p>
&lt;h3 id="長連線服務先看-client-是否會持續留在線上">長連線服務：先看 client 是否會持續留在線上&lt;/h3>
&lt;p>長連線服務的核心特徵是「client 連上來之後不會立刻結束」。判斷時可以看連線是否需要維持數分鐘、數小時，甚至整個工作階段；只要 server 要持續追蹤 client 狀態，就會遇到生命週期管理問題。&lt;/p>
&lt;p>接近真實網路服務的例子包括：&lt;/p>
&lt;ul>
&lt;li>即時聊天室與客服對話&lt;/li>
&lt;li>線上協作文件的多人編輯狀態&lt;/li>
&lt;li>股票、運動比分或遊戲狀態的即時推送&lt;/li>
&lt;li>後台任務進度頁面的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sse/" data-link-title="Server-Sent Events (SSE)" data-link-desc="說明 SSE 如何透過 HTTP 長連線向 client 單向推送事件">SSE&lt;/a> 更新&lt;/li>
&lt;/ul>
&lt;p>這類服務的主要工程問題是「連線會斷、client 會變慢、server 要清理資源」。Go 很適合把 read loop、write loop、heartbeat、subscription、shutdown 拆成不同 goroutine 與 channel 邊界。當連線失效時，&lt;code>context&lt;/code>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline&lt;/a> 與 unregister 流程可以把清理責任收斂到同一個地方。&lt;/p>
&lt;h3 id="背景-worker先看工作是否不適合卡住-request">背景 worker：先看工作是否不適合卡住 request&lt;/h3>
&lt;p>背景 worker 的核心特徵是「工作需要持續處理，並且適合從使用者 request 的等待時間中拆出來」。判斷時可以看某個操作是否需要重試、排程、批次處理，或等待外部系統完成。&lt;/p>
&lt;p>接近真實網路服務的例子包括：&lt;/p>
&lt;ul>
&lt;li>寄送 email、簡訊或推播通知&lt;/li>
&lt;li>影片轉檔、圖片壓縮與報表產生&lt;/li>
&lt;li>每晚同步 CRM、金流或庫存資料&lt;/li>
&lt;li>消費 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> message 並更新內部狀態&lt;/li>
&lt;/ul>
&lt;p>這類服務的主要工程問題是「工作要能開始、停止、重試、記錄錯誤並控制速率」。Go 的 &lt;code>Run(ctx)&lt;/code>、ticker、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool&lt;/a>、channel &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 與 structured &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log&lt;/a> 可以把 worker 生命週期寫清楚。Go 的好處是讓背景流程仍然可取消、可觀測、可測試，而不只是把工作丟到背景。&lt;/p>
&lt;h3 id="事件處理先看系統是否圍繞已發生的事流動">事件處理：先看系統是否圍繞已發生的事流動&lt;/h3>
&lt;p>事件處理的核心特徵是「系統收到某件已發生的事，再依規則更新狀態或觸發後續行為」。判斷時可以看資料是否常以 &lt;code>created&lt;/code>、&lt;code>updated&lt;/code>、&lt;code>failed&lt;/code>、&lt;code>completed&lt;/code> 這類事實形式流動。&lt;/p></description><content:encoded><![CDATA[<p>選擇 Go 的核心判斷是工作場景是否需要長時間運行、明確邊界、穩定併發與簡單部署。這一章用工程條件判斷 Go 是否適合目前問題；若工作更依賴框架模板、快速表單 CRUD、動態行為或大量 runtime magic，其他語言或框架可能更符合需求。</p>
<p>選型文章的目標是建立判斷路徑。讀者未來面對的可能是 API service、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> server、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> worker、內部工具或資料處理流程；同一個語言在不同工作負載下會有不同價值。先理解判斷條件，再進入語法細節，才不會把「我會寫 Go」誤解成「所有問題都該用 Go」。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>判斷哪些工作負載適合 Go</li>
<li>看出哪些系統型態特別適合 Go</li>
<li>區分 Go 的強項與不適合硬上的場景</li>
<li>用工程條件取代語言偏好來做選型</li>
<li>為後續語法與實作章節建立正確的閱讀順序</li>
</ol>
<hr>
<h2 id="觀察先看工作負載再看語言">【觀察】先看工作負載，再看語言</h2>
<p>Go 最常被選中的場景，是需要穩定處理大量服務型工作的系統。這些工作通常包含等待外部 I/O、管理長生命週期、持續處理背景任務或維持清楚服務邊界：</p>
<table>
  <thead>
      <tr>
          <th>工作型態</th>
          <th>為什麼適合 Go</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>高併發 I/O</td>
          <td>goroutine 成本低，適合大量等待型工作</td>
      </tr>
      <tr>
          <td>長連線服務</td>
          <td>容易管理生命週期、取消與資源清理</td>
      </tr>
      <tr>
          <td>背景 worker</td>
          <td>可以把工作拆成小單位並持續處理</td>
      </tr>
      <tr>
          <td>事件處理</td>
          <td>channel、select 與明確邊界很適合事件流</td>
      </tr>
      <tr>
          <td>API service</td>
          <td>標準庫直接支撐 HTTP、context、<a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a> 與 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a></td>
      </tr>
  </tbody>
</table>
<p>如果工作本質就是一堆等待外部 I/O 的操作，Go 往往比把整個問題放進單線程迴圈更自然。</p>
<h3 id="高併發-io先看服務是否長時間等待外部回應">高併發 I/O：先看服務是否長時間等待外部回應</h3>
<p>高併發 I/O 的核心特徵是「同時有很多工作在等待網路、檔案、資料庫或外部 API」。判斷時可以先看 request 的時間花在哪裡：如果大部分時間都在等下游回應，CPU 計算只占一小段，這就是等待型工作。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>API gateway 同時轉送請求到多個下游服務</li>
<li>價格比較網站同時查詢多個供應商 API</li>
<li>檔案上傳服務同時處理大量 client 的上傳進度</li>
<li><a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook</a> receiver 同時接收付款、物流、通知平台的 callback</li>
</ul>
<p>這類服務的主要工程問題是「同時有很多等待中的工作，而且每個工作都需要容量邊界」。Go 的 goroutine 可以讓每個等待中的 request 有清楚的執行單位，<code>context</code> 可以控制 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 與取消，channel 或 semaphore 可以限制同時打到下游的數量。Go 的價值在於讓等待、取消與容量控制都變成明確程式結構。</p>
<h3 id="長連線服務先看-client-是否會持續留在線上">長連線服務：先看 client 是否會持續留在線上</h3>
<p>長連線服務的核心特徵是「client 連上來之後不會立刻結束」。判斷時可以看連線是否需要維持數分鐘、數小時，甚至整個工作階段；只要 server 要持續追蹤 client 狀態，就會遇到生命週期管理問題。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>即時聊天室與客服對話</li>
<li>線上協作文件的多人編輯狀態</li>
<li>股票、運動比分或遊戲狀態的即時推送</li>
<li>後台任務進度頁面的 <a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> / <a href="/blog/backend/knowledge-cards/sse/" data-link-title="Server-Sent Events (SSE)" data-link-desc="說明 SSE 如何透過 HTTP 長連線向 client 單向推送事件">SSE</a> 更新</li>
</ul>
<p>這類服務的主要工程問題是「連線會斷、client 會變慢、server 要清理資源」。Go 很適合把 read loop、write loop、heartbeat、subscription、shutdown 拆成不同 goroutine 與 channel 邊界。當連線失效時，<code>context</code>、<a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 與 unregister 流程可以把清理責任收斂到同一個地方。</p>
<h3 id="背景-worker先看工作是否不適合卡住-request">背景 worker：先看工作是否不適合卡住 request</h3>
<p>背景 worker 的核心特徵是「工作需要持續處理，並且適合從使用者 request 的等待時間中拆出來」。判斷時可以看某個操作是否需要重試、排程、批次處理，或等待外部系統完成。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>寄送 email、簡訊或推播通知</li>
<li>影片轉檔、圖片壓縮與報表產生</li>
<li>每晚同步 CRM、金流或庫存資料</li>
<li>消費 <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> message 並更新內部狀態</li>
</ul>
<p>這類服務的主要工程問題是「工作要能開始、停止、重試、記錄錯誤並控制速率」。Go 的 <code>Run(ctx)</code>、ticker、<a href="/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool</a>、channel <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 與 structured <a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a> 可以把 worker 生命週期寫清楚。Go 的好處是讓背景流程仍然可取消、可觀測、可測試，而不只是把工作丟到背景。</p>
<h3 id="事件處理先看系統是否圍繞已發生的事流動">事件處理：先看系統是否圍繞已發生的事流動</h3>
<p>事件處理的核心特徵是「系統收到某件已發生的事，再依規則更新狀態或觸發後續行為」。判斷時可以看資料是否常以 <code>created</code>、<code>updated</code>、<code>failed</code>、<code>completed</code> 這類事實形式流動。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>訂單付款成功後更新訂單狀態並發送通知</li>
<li>使用者註冊完成後建立歡迎流程與分析事件</li>
<li>CI job 狀態改變後推送到 <a href="/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">dashboard</a></li>
<li>IoT 裝置上報 sensor reading 後觸發告警</li>
</ul>
<p>這類服務的主要工程問題是「事件來源多、順序可能不同、重複事件需要處理」。Go 的型別可以定義穩定 event envelope，channel 或 <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> adapter 可以把來源收斂到 processor，processor 再集中處理 validation、dedup、state transition 與 publish。Go 的價值在於讓事件流的每一段責任清楚可測。</p>
<h3 id="api-service先看服務是否需要清楚的-request-邊界">API service：先看服務是否需要清楚的 request 邊界</h3>
<p>API service 的核心特徵是「外部 client 用明確 request 取得資料或要求系統執行動作」。判斷時可以看服務是否需要穩定路由、輸入驗證、timeout、error response、<a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a> 與 <a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a>。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>手機 App 的會員、訂單、通知 API</li>
<li>SaaS 產品提供給客戶整合的 public API</li>
<li>內部微服務之間的 HTTP/gRPC API</li>
<li><a href="/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">dashboard</a> 查詢目前狀態與操作後端任務的 API</li>
</ul>
<p>這類服務的主要工程問題是「request 進來後要有清楚邊界」。Go 標準庫的 <code>net/http</code>、<code>context</code>、<code>encoding/json</code>、<code>log/slog</code> 與 testing package 已經提供服務骨架需要的基本能力。當 API 邊界清楚時，handler 可以專注在傳輸格式，usecase 處理行為規則，repository 或 external client 處理資料依賴。</p>
<p>例如一個通知服務需要同時處理三件事：接收 HTTP callback、把事件放進背景處理流程、再把結果推送給已訂閱的 client。這個服務的主要成本通常在於同時等待網路、管理 client 連線、控制 queue 滿載與清理失效資源；單次計算反而只是其中一小部分。Go 的 goroutine、channel、context 與標準庫 HTTP 可以把這些生命週期寫成明確的程式結構。</p>
<p>相反地，一個只需要三個表單頁面、幾個後台列表和現成權限模板的內部管理系統，主要成本可能在 UI、表單驗證、ORM convention 與後台 scaffolding。這種工作也能用 Go 完成，但選型時應先問：「主要成本是在服務生命週期，還是在框架已經提供的業務頁面組裝？」這個問題比語言效能更接近真正瓶頸。</p>
<h2 id="判讀架構邊界是否清楚">【判讀】架構邊界是否清楚</h2>
<p>Go 特別適合邊界清楚的後端服務。當一個系統可以自然拆成輸入、協調、狀態、輸出幾層時，Go 的 struct、interface、package 與明確依賴會讓責任更容易看見。</p>
<p>例如以下這類系統通常是 Go 的好候選：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> 即時服務</li>
<li>notification service</li>
<li>queue <a href="/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer</a></li>
<li>log / event pipeline</li>
<li>需要清楚 ports/adapters 的 backend service</li>
</ul>
<p>產品若高度依賴框架提供的大量現成功能，或核心價值在於快速拼接大量業務頁面與模板，選型時應把框架生態列為主要條件。這類情境可以先評估 Python、Ruby、JavaScript/TypeScript 或其他更貼近既有生態的方案。</p>
<p>判斷架構邊界時，可以先畫出資料如何通過系統：</p>
<ol>
<li>外部請求或事件從哪裡進來</li>
<li>哪一層負責驗證與轉換</li>
<li>哪一層負責狀態轉移或業務規則</li>
<li>哪一層負責回應、推送或記錄</li>
</ol>
<p>如果這四個問題能自然拆成幾個責任清楚的元件，Go 會讓這些邊界很容易被程式碼表達。handler 可以處理傳輸格式，usecase 可以處理行為規則，repository 或 state owner 可以處理狀態，publisher 或 response layer 可以處理輸出。這種設計不需要大型框架先定義所有路徑，Go 的簡單型別與小介面就能支撐。</p>
<p>如果團隊還在探索商業流程，連資料模型、頁面流程與權限規則都會每天改，框架的 convention 可能更重要。這時候語言選型的重點是「顯式設計的成本是否值得現在承擔」。Go 會鼓勵你把邊界寫清楚；當邊界本身仍在頻繁變動，這種清楚有時會變成前置設計成本。</p>
<h2 id="策略runtime-與部署條件也是選型的一部分">【策略】runtime 與部署條件也是選型的一部分</h2>
<p>Go 的優勢不只是語法，還包括 runtime 與部署形態：</p>
<ul>
<li>單一 binary，部署流程簡單</li>
<li>啟動速度快，適合 container 與短週期交付</li>
<li>標準庫完整，很多服務不需要先找一堆框架</li>
<li>可讀性高，長期維護成本較容易控制</li>
</ul>
<p>如果你的團隊很在意：</p>
<ul>
<li>記憶體用量</li>
<li>啟動時間</li>
<li>觀測與除錯</li>
<li>服務在高流量下的穩定性</li>
</ul>
<p>那 Go 的工程價值就會很明顯。</p>
<p>部署條件會影響語言價值，因為服務最終要在開發機之外的環境長期運行。假設一個團隊要把多個小服務放進 container，每個服務都需要 <a href="/blog/backend/knowledge-cards/health-check-liveness/" data-link-title="Liveness" data-link-desc="說明平台如何判斷 process 是否仍然存活，以及何時應重啟">health check</a>、timeout、structured log、<a href="/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">graceful shutdown</a> 與固定資源限制。Go 的單一 binary 和標準庫讓這些能力可以用相對少的外部依賴完成；服務啟動、部署與回滾也比較容易被平台工程師理解。</p>
<p>另一個常見例子是 CLI 工具或 sidecar service。這類程式常被放進 CI、Kubernetes job、systemd service 或部署腳本中。Go 編譯後的 binary 可以降低 runtime 安裝與版本衝突問題。這是交付形態優勢：當程式要在很多環境中穩定啟動，少一層 runtime 依賴就是一個可觀的工程收益。</p>
<h2 id="執行哪些情況要先評估其他方案">【執行】哪些情況要先評估其他方案</h2>
<p>選型的執行規則是先確認主要瓶頸，再決定語言。以下情境通常應先評估其他語言、框架或平台能力：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>極度偏 CRUD 模板系統</td>
          <td>框架生態可能比語言特性更重要</td>
      </tr>
      <tr>
          <td>大量動態行為與 runtime 配置</td>
          <td>Go 會要求更多顯式設計</td>
      </tr>
      <tr>
          <td>團隊主要目標是快速試錯</td>
          <td>Go 的工程紀律可能比腳本型語言更有前置成本</td>
      </tr>
      <tr>
          <td>主要瓶頸在前端整合流程</td>
          <td>主要解法在前端工具鏈、元件生態與產品流程</td>
      </tr>
  </tbody>
</table>
<p>Go 可以處理其中部分情境，但它的工程價值未必對準主要瓶頸。當主要成本在框架生態、動態流程或前端整合時，下一步應先比較那些領域更成熟的工具。</p>
<h3 id="極度偏-crud-模板系統先看頁面是否圍繞資料表轉">極度偏 CRUD 模板系統：先看頁面是否圍繞資料表轉</h3>
<p>CRUD 模板系統的核心特徵是「大部分功能都在新增、查詢、修改、刪除資料」。判斷時可以先看產品畫面：如果主要頁面都是列表、篩選、表單、詳情頁、權限設定與匯出報表，系統很可能偏 CRUD。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>電商平台的商品、訂單、會員、優惠券後台</li>
<li>餐廳訂位系統的店家、桌位、時段、訂單管理</li>
<li>客服工單系統的 ticket 列表、狀態修改、負責人指派</li>
<li>活動報名系統的活動、票種、參加者、付款狀態管理</li>
</ul>
<p>這類系統的主要工作通常在「快速產生穩定後台功能」。框架如果已經提供 <a href="/blog/backend/knowledge-cards/authentication/" data-link-title="Authentication" data-link-desc="說明系統如何確認呼叫者身份">authentication</a>、<a href="/blog/backend/knowledge-cards/authorization/" data-link-title="Authorization" data-link-desc="說明授權如何判斷誰能對哪些資源執行哪些操作">authorization</a>、ORM、form validation、admin scaffolding、pagination 與 search，團隊會先從框架生態獲得產能。Go 仍然可以負責其中的高流量 API、背景同步、付款 callback 或報表生成，但整個後台產品的主體未必需要先用 Go 開始。</p>
<p>判斷問題可以這樣問：如果拿掉後台表單、列表與權限頁，系統還剩下什麼核心工程問題？如果答案很少，框架模板可能就是主要能力。</p>
<h3 id="大量動態行為與-runtime-配置先看規則是否由使用者定義">大量動態行為與 runtime 配置：先看規則是否由使用者定義</h3>
<p>動態行為系統的核心特徵是「行為在執行期間由設定、腳本、規則或使用者操作改變」。判斷時可以先觀察：工程師是否常常需要讓非工程使用者自己新增欄位、調整流程、改驗證規則或配置通知條件。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>表單建置器：使用者可以自己新增欄位、驗證規則與送出後動作</li>
<li>工作流系統：管理者可以設定「訂單超過金額就送審」、「狀態改變就寄信」</li>
<li>CMS：編輯可以建立不同內容模型、欄位與發布流程</li>
<li>行銷自動化工具：使用者可以用條件組合出不同受眾與觸發規則</li>
</ul>
<p>這類系統的主要工程問題在於「如何安全表達動態規則」。Go 的靜態型別會鼓勵你把資料結構與行為先定義清楚；這對穩定服務很有價值，但對高度動態的產品，可能需要額外設計 rule engine、schema registry、plugin boundary 或 DSL。若產品核心就是讓使用者自由配置流程，選型時應把動態模型能力列為主要評估項目。</p>
<p>Go 的合理位置通常在規則執行引擎、事件處理器或高併發 delivery service。管理介面、規則編輯器與 schema 設計器則可能更依賴前端與動態框架生態。</p>
<h3 id="團隊主要目標是快速試錯先看需求是否每天改方向">團隊主要目標是快速試錯：先看需求是否每天改方向</h3>
<p>快速試錯情境的核心特徵是「產品問題尚未被驗證」。判斷時可以觀察需求文件與會議紀錄：如果資料模型、頁面流程、定價方式、權限規則與使用者角色都還在頻繁改，團隊此時最需要的是降低改方向的成本。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>新創產品的第一版 MVP</li>
<li>內部工具的概念驗證</li>
<li>尚未確定商業模式的 marketplace</li>
<li>正在測試轉換率的報名、購買或 onboarding 流程</li>
</ul>
<p>這類階段的主要問題是「學到使用者真正需要什麼」。腳本型語言、full-stack framework、低程式碼工具或現成 SaaS 可能更適合先驗證流程。Go 的型別、錯誤處理與顯式邊界會提高長期可維護性，但在問題尚未穩定時，過早建立完整邊界可能會讓每次方向調整都需要較多工程變更。</p>
<p>Go 仍然可以在試錯產品中出現，但通常適合放在已經確定會留下的部分，例如 <a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook</a> receiver、背景任務、匯入匯出服務或需要穩定運行的小型 API。產品流程本身可以先用更容易改動的工具探索。</p>
<h3 id="主要瓶頸在前端整合流程先看使用者價值是否發生在介面">主要瓶頸在前端整合流程：先看使用者價值是否發生在介面</h3>
<p>前端整合型產品的核心特徵是「使用者價值主要發生在互動介面」。判斷時可以先看產品成功條件：如果最重要的是頁面轉換率、互動動畫、表單體驗、SEO、設計系統、第三方前端 SDK 或多裝置呈現，後端語言通常只是整體解法的一部分。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>行銷 landing page 與 A/B testing 流程</li>
<li>電商結帳頁、購物車、折扣碼與付款 UI</li>
<li>內容網站、文件站、會員訂閱頁</li>
<li>需要大量拖拉、預覽、即時編輯的設計工具</li>
</ul>
<p>這類系統的主要工程問題在於前端狀態、元件設計、瀏覽器限制、SEO、分析追蹤與使用者流程。Go 可以提供穩定 API、授權、訂單狀態或事件接收，但選型時要先確認後端 runtime 是否真的在解主要瓶頸。若瓶頸集中在 UI 與產品流程，Next.js、Remix、Nuxt 或其他前端框架生態可能是更直接的評估起點。</p>
<p>判斷問題可以這樣問：使用者感受到的主要差異來自 server 的併發能力，還是來自畫面反應、表單流程、SEO 與第三方整合？如果主要差異在後者，Go 可以是後端配角，不一定是產品主體的第一個選型決策。</p>
<p>選型可以用三個問題收斂：</p>
<ol>
<li><strong>主要瓶頸是什麼？</strong> 如果瓶頸是大量 I/O、長連線、背景處理、部署穩定性，Go 值得優先評估；如果瓶頸是 UI scaffolding、資料後台或快速試錯，框架生態可能更關鍵。</li>
<li><strong>邊界是否已經清楚？</strong> 如果輸入、規則、狀態與輸出能被穩定拆開，Go 的顯式設計會帶來可讀性；如果流程每天改，先用 convention 強的工具驗證產品可能更合理。</li>
<li><strong>團隊要優化短期探索還是長期維護？</strong> Go 會把錯誤處理、型別、依賴與生命週期攤開寫清楚；這對長期服務是優點，對一次性探索則可能顯得偏重。</li>
</ol>
<p>因此，選 Go 的結論應該長得像工程判斷。例如：「這個服務會維持上千條長連線，需要明確 timeout、<a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 與 <a href="/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">shutdown</a> 所以 Go 是好候選。」或是：「這個系統主要是後台 CRUD 和權限頁面，框架產能比 runtime 特性更重要，所以先評估 Django、Rails 或 Next.js 生態。」這樣的句子能讓團隊看見選型依據，也能在條件改變時重新評估。</p>
]]></content:encoded></item><item><title>成本思維：軟體開發的隱性代價</title><link>https://tarrragon.github.io/blog/python/00-philosophy/cost-thinking/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/cost-thinking/</guid><description>&lt;h2 id="什麼是軟體開發的成本">什麼是軟體開發的成本？&lt;/h2>
&lt;p>當我們談論軟體開發的「成本」，大多數人想到的是開發時間：「這個功能需要多少工時？」&lt;/p>
&lt;p>但這只是冰山一角。&lt;/p>
&lt;h3 id="顯性成本-vs-隱性成本">顯性成本 vs 隱性成本&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>成本類型&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;th>容易被看見？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開發時間&lt;/td>
 &lt;td>寫程式碼、除錯&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護成本&lt;/td>
 &lt;td>修改 11 處重複程式碼&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復成本&lt;/td>
 &lt;td>自訂實作引入 bug 後的 hotfix&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗成本&lt;/td>
 &lt;td>任務失敗後的重試和浪費&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施債務&lt;/td>
 &lt;td>缺乏可觀測性導致的除錯時間&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計決策的長期代價&lt;/td>
 &lt;td>選擇了不適當的清理頻率&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>隱性成本的特點是：決策當下看不見，但會在未來反覆出現。&lt;/p>
&lt;h3 id="成本思維的核心問題">成本思維的核心問題&lt;/h3>
&lt;p>每次做技術決策時，問自己：&lt;/p>
&lt;blockquote>
&lt;p>這個決策的「總成本」是多少？不只是現在的開發成本，還包括未來的維護、修復、擴展成本。&lt;/p>&lt;/blockquote>
&lt;p>這就是成本思維的本質：&lt;strong>把時間軸拉長來評估決策。&lt;/strong>&lt;/p>
&lt;h2 id="重新造輪子的真實成本">重新造輪子的真實成本&lt;/h2>
&lt;h3 id="一個看似合理的決策">一個看似合理的決策&lt;/h3>
&lt;p>假設你需要一個「延遲建立檔案」的日誌 Handler &amp;ndash; 只有在真正寫入日誌時才建立檔案，避免產生空的日誌檔。&lt;/p>
&lt;p>你可能會這樣想：「標準庫的 FileHandler 不支援延遲建立，我自己寫一個。」&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">class&lt;/span> &lt;span class="nc">LazyFileHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FileHandler&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;延遲建立檔案的 Handler&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="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">filename&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 不呼叫 super().__init__() 以避免建立檔案&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">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Handler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">record&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">makedirs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">exist_ok&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">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&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">15&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">record&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="c1"># AttributeError: &amp;#39;LazyFileHandler&amp;#39; has no attribute &amp;#39;stream&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="隱藏的成本鏈">隱藏的成本鏈&lt;/h3>
&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">1. 開發成本：寫自訂類別 ~30 分鐘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2. 除錯成本：追蹤 AttributeError ~1 小時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">3. 修復成本：派發 hotfix 任務 ~2 小時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">4. 驗證成本：確認修復後無迴歸 ~30 分鐘
&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"> 總成本：~4 小時&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="標準庫方案">標準庫方案&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 一行解決&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FileHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delay&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">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># delay=True：延遲到第一次 emit 時才建立檔案&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"># Python 3.0 就已存在，經過 15+ 年的穩定性驗證&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>開發成本：約 1 分鐘。維護成本：零。修復成本：零。&lt;/p>
&lt;h3 id="成本對比">成本對比&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>自訂 LazyFileHandler&lt;/th>
 &lt;th>標準庫 delay=True&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開發時間&lt;/td>
 &lt;td>30 分鐘&lt;/td>
 &lt;td>1 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式碼行數&lt;/td>
 &lt;td>20+ 行&lt;/td>
 &lt;td>1 行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試需求&lt;/td>
 &lt;td>需要自行測試&lt;/td>
 &lt;td>標準庫已驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bug 風險&lt;/td>
 &lt;td>高（跳過 super 初始化）&lt;/td>
 &lt;td>極低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護成本&lt;/td>
 &lt;td>需要持續維護&lt;/td>
 &lt;td>零&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>總成本&lt;/td>
 &lt;td>~4 小時&lt;/td>
 &lt;td>~1 分鐘&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>教訓：在寫任何自訂實作之前，先花 5 分鐘搜尋標準庫。這 5 分鐘的投資，可能節省數小時的維護和除錯成本。&lt;/p>
&lt;h2 id="重複程式碼的累積成本">重複程式碼的累積成本&lt;/h2>
&lt;h3 id="從-1-處到-11-處">從 1 處到 11 處&lt;/h3>
&lt;p>一個簡單的函式，從 stdin 讀取 JSON：&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"># 這段程式碼出現在 11 個 Hook 檔案中&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">read_json_from_stdin&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="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">json&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">try&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">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="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">6&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>當它只出現在 1 個檔案中時，問題不大。但隨著 Hook 數量增加，這段程式碼被複製到了 11 個檔案。&lt;/p>
&lt;h3 id="累積成本的計算">累積成本的計算&lt;/h3>
&lt;p>假設有一天你需要修改這個函式的行為（例如加入錯誤日誌記錄）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 修改後的版本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_json_from_stdin&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="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">json&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">logging&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">logger&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getLogger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__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="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warning&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;stdin JSON 解析失敗: &lt;/span>&lt;span class="si">%s&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&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">12&lt;/span>&lt;span class="cl"> &lt;span class="n">logger&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;stdin 讀取異常: &lt;/span>&lt;span class="si">%s&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>1 份程式碼&lt;/th>
 &lt;th>11 份重複&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>修改次數&lt;/td>
 &lt;td>1&lt;/td>
 &lt;td>11&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試次數&lt;/td>
 &lt;td>1&lt;/td>
 &lt;td>11&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遺漏風險&lt;/td>
 &lt;td>0%&lt;/td>
 &lt;td>~20%（經驗值）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>行為不一致風險&lt;/td>
 &lt;td>無&lt;/td>
 &lt;td>有&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式碼審查成本&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="指數增長的維護成本">指數增長的維護成本&lt;/h3>
&lt;p>重複程式碼的成本隨著時間呈指數增長：&lt;/p></description><content:encoded><![CDATA[<h2 id="什麼是軟體開發的成本">什麼是軟體開發的成本？</h2>
<p>當我們談論軟體開發的「成本」，大多數人想到的是開發時間：「這個功能需要多少工時？」</p>
<p>但這只是冰山一角。</p>
<h3 id="顯性成本-vs-隱性成本">顯性成本 vs 隱性成本</h3>
<table>
  <thead>
      <tr>
          <th>成本類型</th>
          <th>例子</th>
          <th>容易被看見？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發時間</td>
          <td>寫程式碼、除錯</td>
          <td>是</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>修改 11 處重複程式碼</td>
          <td>否</td>
      </tr>
      <tr>
          <td>修復成本</td>
          <td>自訂實作引入 bug 後的 hotfix</td>
          <td>否</td>
      </tr>
      <tr>
          <td>失敗成本</td>
          <td>任務失敗後的重試和浪費</td>
          <td>否</td>
      </tr>
      <tr>
          <td>基礎設施債務</td>
          <td>缺乏可觀測性導致的除錯時間</td>
          <td>否</td>
      </tr>
      <tr>
          <td>設計決策的長期代價</td>
          <td>選擇了不適當的清理頻率</td>
          <td>否</td>
      </tr>
  </tbody>
</table>
<p>隱性成本的特點是：決策當下看不見，但會在未來反覆出現。</p>
<h3 id="成本思維的核心問題">成本思維的核心問題</h3>
<p>每次做技術決策時，問自己：</p>
<blockquote>
<p>這個決策的「總成本」是多少？不只是現在的開發成本，還包括未來的維護、修復、擴展成本。</p></blockquote>
<p>這就是成本思維的本質：<strong>把時間軸拉長來評估決策。</strong></p>
<h2 id="重新造輪子的真實成本">重新造輪子的真實成本</h2>
<h3 id="一個看似合理的決策">一個看似合理的決策</h3>
<p>假設你需要一個「延遲建立檔案」的日誌 Handler &ndash; 只有在真正寫入日誌時才建立檔案，避免產生空的日誌檔。</p>
<p>你可能會這樣想：「標準庫的 FileHandler 不支援延遲建立，我自己寫一個。」</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">class</span> <span class="nc">LazyFileHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;延遲建立檔案的 Handler&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">filename</span> <span class="o">=</span> <span class="n">filename</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">mode</span> <span class="o">=</span> <span class="n">mode</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_file_created</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 不呼叫 super().__init__() 以避免建立檔案</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">logging</span><span class="o">.</span><span class="n">Handler</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">emit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</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="bp">self</span><span class="o">.</span><span class="n">_file_created</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">),</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_file_created</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># AttributeError: &#39;LazyFileHandler&#39; has no attribute &#39;stream&#39;</span></span></span></code></pre></div><h3 id="隱藏的成本鏈">隱藏的成本鏈</h3>
<p>這段程式碼引發了一連串的成本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 開發成本：寫自訂類別          ~30 分鐘
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 除錯成本：追蹤 AttributeError  ~1 小時
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 修復成本：派發 hotfix 任務      ~2 小時
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 驗證成本：確認修復後無迴歸      ~30 分鐘
</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">   總成本：~4 小時</span></span></code></pre></div><h3 id="標準庫方案">標準庫方案</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 一行解決</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">delay</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># delay=True：延遲到第一次 emit 時才建立檔案</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Python 3.0 就已存在，經過 15+ 年的穩定性驗證</span></span></span></code></pre></div><p>開發成本：約 1 分鐘。維護成本：零。修復成本：零。</p>
<h3 id="成本對比">成本對比</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>自訂 LazyFileHandler</th>
          <th>標準庫 delay=True</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發時間</td>
          <td>30 分鐘</td>
          <td>1 分鐘</td>
      </tr>
      <tr>
          <td>程式碼行數</td>
          <td>20+ 行</td>
          <td>1 行</td>
      </tr>
      <tr>
          <td>測試需求</td>
          <td>需要自行測試</td>
          <td>標準庫已驗證</td>
      </tr>
      <tr>
          <td>Bug 風險</td>
          <td>高（跳過 super 初始化）</td>
          <td>極低</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>需要持續維護</td>
          <td>零</td>
      </tr>
      <tr>
          <td>總成本</td>
          <td>~4 小時</td>
          <td>~1 分鐘</td>
      </tr>
  </tbody>
</table>
<p>教訓：在寫任何自訂實作之前，先花 5 分鐘搜尋標準庫。這 5 分鐘的投資，可能節省數小時的維護和除錯成本。</p>
<h2 id="重複程式碼的累積成本">重複程式碼的累積成本</h2>
<h3 id="從-1-處到-11-處">從 1 處到 11 處</h3>
<p>一個簡單的函式，從 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="c1"># 這段程式碼出現在 11 個 Hook 檔案中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">read_json_from_stdin</span><span class="p">():</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 class="o">,</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</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">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><p>當它只出現在 1 個檔案中時，問題不大。但隨著 Hook 數量增加，這段程式碼被複製到了 11 個檔案。</p>
<h3 id="累積成本的計算">累積成本的計算</h3>
<p>假設有一天你需要修改這個函式的行為（例如加入錯誤日誌記錄）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 修改後的版本</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">read_json_from_stdin</span><span class="p">():</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 class="o">,</span> <span class="nn">json</span><span class="o">,</span> <span class="nn">logging</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">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 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;stdin JSON 解析失敗: </span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">e</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="p">{}</span>
</span></span><span class="line"><span class="ln">11</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">12</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;stdin 讀取異常: </span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>1 份程式碼</th>
          <th>11 份重複</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>修改次數</td>
          <td>1</td>
          <td>11</td>
      </tr>
      <tr>
          <td>測試次數</td>
          <td>1</td>
          <td>11</td>
      </tr>
      <tr>
          <td>遺漏風險</td>
          <td>0%</td>
          <td>~20%（經驗值）</td>
      </tr>
      <tr>
          <td>行為不一致風險</td>
          <td>無</td>
          <td>有</td>
      </tr>
      <tr>
          <td>程式碼審查成本</td>
          <td>低</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<h3 id="指數增長的維護成本">指數增長的維護成本</h3>
<p>重複程式碼的成本隨著時間呈指數增長：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">第 1 次修改：11 處 x 5 分鐘 = 55 分鐘
</span></span><span class="line"><span class="ln">2</span><span class="cl">第 2 次修改：11 處 x 5 分鐘 + 排查第 1 次遺漏的 bug = 75 分鐘
</span></span><span class="line"><span class="ln">3</span><span class="cl">第 3 次修改：11 處 x 5 分鐘 + 排查前兩次的行為不一致 = 120 分鐘
</span></span><span class="line"><span class="ln">4</span><span class="cl">...</span></span></code></pre></div><p>每次遺漏一處修改，就會引入一個「行為不一致」的隱性 bug。這些 bug 不會立即爆發，而是在某個不相關的除錯過程中突然出現，讓你花數小時追蹤一個「不應該存在」的問題。</p>
<h3 id="正確做法提前提取">正確做法：提前提取</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># lib/hook_io.py（共用模組）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">read_json_from_stdin</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    從 stdin 讀取 JSON 資料。
</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">    Returns:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        解析後的字典，失敗時返回空字典
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">json</span><span class="o">,</span> <span class="nn">logging</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">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</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;stdin JSON 解析失敗: </span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">17</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">18</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;stdin 讀取異常: </span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">,</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="p">{}</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 每個 Hook 檔案中</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_json_from_stdin</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">input_data</span> <span class="o">=</span> <span class="n">read_json_from_stdin</span><span class="p">()</span></span></span></code></pre></div><p>修改 1 處，所有 11 個 Hook 自動生效。</p>
<p>教訓：DRY 不只是「不要重複自己」的美學追求，而是一個成本控制策略。重複程式碼的維護成本會隨時間加速增長。</p>
<h2 id="可觀測性看不見的基礎設施">可觀測性：看不見的基礎設施</h2>
<h3 id="一個真實的場景">一個真實的場景</h3>
<p>想像一個有 20 個 Hook 的系統，某天你發現有 7 個 Hook 靜默失敗了 &ndash; 沒有錯誤訊息，沒有日誌，就是安靜地不做事。而且這個情況已經持續了至少 2 個 session（數小時）。</p>
<p>你怎麼發現的？靠偶然的手動檢查，監控系統沒有抓到。</p>
<h3 id="為什麼會靜默失敗">為什麼會靜默失敗？</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 「安全」的錯誤處理（實際上是最危險的）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook_safely</span><span class="p">(</span><span class="n">hook_func</span><span class="p">):</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="n">hook_func</span><span class="p">()</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="c1"># 只寫入檔案日誌，不通知任何人</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">log_to_file</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></code></pre></div><p>這段程式碼的意圖是「不要讓 Hook 失敗影響主流程」。但它的副作用是：<strong>你完全不知道 Hook 有沒有在正常運作。</strong></p>
<h3 id="沒有可觀測性的除錯成本">沒有可觀測性的除錯成本</h3>
<p>當問題最終被發現時，除錯過程是這樣的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 發現問題            0 分鐘（偶然發現，否則可能更久）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 確認哪些 Hook 失敗    30 分鐘（需要手動逐一檢查）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 找到失敗原因          2 小時（沒有日誌可看，只能猜測）
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 修復失敗的 Hook       1 小時
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 驗證修復效果          30 分鐘
</span></span><span class="line"><span class="ln">6</span><span class="cl">6. 確認沒有其他受影響的部分  1 小時
</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">   總成本：~5 小時（且可能仍有遺漏）</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="k">def</span> <span class="nf">run_hook_safely</span><span class="p">(</span><span class="n">hook_func</span><span class="p">,</span> <span class="n">hook_name</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">hook_func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">except</span> <span class="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">5</span><span class="cl">        <span class="c1"># 寫入檔案日誌（完整追蹤）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">log_to_file</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 class="n">traceback</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="c1"># 輸出到 stderr（確保使用者可見）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[Hook Error] </span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</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">1. 發現問題           0 分鐘（stderr 立即可見）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 確認失敗原因        5 分鐘（日誌有完整的 traceback）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 修復失敗的 Hook     30 分鐘
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 驗證修復效果        10 分鐘
</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">   總成本：~45 分鐘</span></span></code></pre></div><h3 id="投資回報分析">投資回報分析</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>無可觀測性</th>
          <th>有可觀測性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>前期投資</td>
          <td>0 小時</td>
          <td>~8 小時（建設日誌架構）</td>
      </tr>
      <tr>
          <td>每次除錯</td>
          <td>~5 小時</td>
          <td>~45 分鐘</td>
      </tr>
      <tr>
          <td>3 次事故後總成本</td>
          <td>15 小時</td>
          <td>8 + 2.25 = 10.25 小時</td>
      </tr>
      <tr>
          <td>5 次事故後總成本</td>
          <td>25 小時</td>
          <td>8 + 3.75 = 11.75 小時</td>
      </tr>
      <tr>
          <td>問題發現延遲</td>
          <td>數小時到數天</td>
          <td>即時</td>
      </tr>
  </tbody>
</table>
<p>只要遇到 3 次以上的事故，可觀測性投資就開始回本。而在任何有一定規模的系統中，問題出現 3 次幾乎是必然的。</p>
<p>教訓：可觀測性是「看不見的基礎設施」。它的缺失不會直接造成 bug，但會讓每個 bug 的修復成本倍增。</p>
<h2 id="系統設計中的頻率取捨">系統設計中的頻率取捨</h2>
<h3 id="問題背景">問題背景</h3>
<p>一個 Hook 系統每次執行都會產生日誌檔案。隨著時間累積，過期的日誌需要被清理。問題是：<strong>多久清理一次？</strong></p>
<h3 id="三種方案的成本比較">三種方案的成本比較</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 方案 A：每次都清理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">execute_hook_logic</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cleanup_old_logs</span><span class="p">()</span>  <span class="c1"># 每次 Hook 執行後都清理</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"># 方案 B：每 N 次清理一次</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">execute_hook_logic</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">state</span><span class="p">[</span><span class="s2">&#34;execution_count&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">state</span><span class="p">[</span><span class="s2">&#34;execution_count&#34;</span><span class="p">]</span> <span class="o">%</span> <span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">cleanup_old_logs</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 方案 C：外部排程清理</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 由 cron job 或系統排程器負責</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># Hook 本身不做任何清理</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>方案 A：每次清理</th>
          <th>方案 B：每 N 次</th>
          <th>方案 C：外部排程</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>I/O 成本</td>
          <td>高（每次都掃描目錄）</td>
          <td>低（每 10 次一次）</td>
          <td>零（Hook 無關）</td>
      </tr>
      <tr>
          <td>精確度</td>
          <td>高（即時清理）</td>
          <td>中（最多延遲 10 次）</td>
          <td>高（可設定精確排程）</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>低</td>
          <td>中（需要計數器）</td>
          <td>高（需要外部依賴）</td>
      </tr>
      <tr>
          <td>對 Hook 效能影響</td>
          <td>有（每次增加 I/O）</td>
          <td>小</td>
          <td>無</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>低</td>
          <td>低</td>
          <td>中（需維護排程設定）</td>
      </tr>
  </tbody>
</table>
<h3 id="決策依據找到平衡點">決策依據：找到平衡點</h3>
<p>方案 B 被選中，原因是：</p>
<ol>
<li><strong>I/O 成本可控</strong> &ndash; 每 10 次才觸發一次，對效能影響極小</li>
<li><strong>精確度可接受</strong> &ndash; 日誌多存留幾次不是關鍵問題</li>
<li><strong>零外部依賴</strong> &ndash; 不需要額外的 cron 配置</li>
<li><strong>實作簡單</strong> &ndash; 一個計數器加一個 if 判斷</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">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">maybe_cleanup_logs</span><span class="p">(</span><span class="n">execution_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">log_dir</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    根據執行次數決定是否清理舊日誌。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    每 LOG_CLEANUP_TRIGGER_FREQUENCY 次觸發一次清理，
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    在精確度和 I/O 成本之間取得平衡。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="n">execution_count</span> <span class="o">%</span> <span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">cleanup_old_logs</span><span class="p">(</span><span class="n">log_dir</span><span class="p">)</span></span></span></code></pre></div><p>教訓：「最佳方案」不存在，只有「在當前限制條件下成本最低的方案」。頻率問題的本質是精確度和成本之間的取捨。</p>
<h2 id="失敗的成本">失敗的成本</h2>
<h3 id="預驗證-vs-失敗重試">預驗證 vs 失敗重試</h3>
<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"># 方案 A：直接執行，失敗再處理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">dispatch_task</span><span class="p">(</span><span class="n">task</span><span class="p">):</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="n">result</span> <span class="o">=</span> <span class="n">execute</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>  <span class="c1"># 消耗資源</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">except</span> <span class="ne">PermissionError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 失敗了，資源已經浪費</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">log</span><span class="p">(</span><span class="s2">&#34;任務失敗：權限不足&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 方案 B：預先驗證</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">dispatch_task</span><span class="p">(</span><span class="n">task</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">has_required_permissions</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">log</span><span class="p">(</span><span class="s2">&#34;跳過：權限不足&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">execute</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>  <span class="c1"># 確認可行才消耗資源</span></span></span></code></pre></div><h3 id="真實場景">真實場景</h3>
<p>兩個探索任務被派發去存取跨專案的資源，但都因為權限限制而失敗。每個任務各消耗了大量運算資源，但結果為零 &ndash; 完全浪費。</p>
<p>如果在派發前花 1 分鐘確認權限，就能避免這些浪費。</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">預驗證成本 = 驗證時間 x 每次派發
</span></span><span class="line"><span class="ln">2</span><span class="cl">失敗成本 = 任務執行時間 x 失敗機率
</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">當 失敗成本 &gt; 預驗證成本 時，預驗證是值得的</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>場景</th>
          <th>預驗證成本</th>
          <th>失敗成本</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>快速本地操作</td>
          <td>高（相對於操作本身）</td>
          <td>低</td>
          <td>不需預驗證</td>
      </tr>
      <tr>
          <td>耗時遠端操作</td>
          <td>低（相對於操作本身）</td>
          <td>高</td>
          <td>必須預驗證</td>
      </tr>
      <tr>
          <td>高失敗率操作</td>
          <td>低</td>
          <td>高</td>
          <td>必須預驗證</td>
      </tr>
      <tr>
          <td>低失敗率操作</td>
          <td>中</td>
          <td>低</td>
          <td>視情況而定</td>
      </tr>
  </tbody>
</table>
<p>教訓：失敗不是免費的。每次失敗都消耗了資源、時間和注意力。預驗證是一種「用小成本避免大浪費」的投資。</p>
<h2 id="歸納成本思維的核心原則">歸納：成本思維的核心原則</h2>
<h3 id="原則一計算總成本不只是開發成本">原則一：計算總成本，不只是開發成本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">總成本 = 開發成本 + 維護成本 + 修復成本 + 機會成本</span></span></code></pre></div><p>一個「快速完成」的方案，如果未來每次修改都要花 3 倍時間，那它其實是最昂貴的方案。</p>
<h3 id="原則二重複的成本會指數增長">原則二：重複的成本會指數增長</h3>
<p>每一份重複的程式碼都是一顆定時炸彈。它的爆炸威力隨著修改次數和時間而增長。</p>
<h3 id="原則三先搜尋再建造">原則三：先搜尋再建造</h3>
<p>在寫任何自訂實作之前，先花 5 分鐘搜尋：</p>
<ul>
<li>標準庫有沒有這個功能？</li>
<li>專案中有沒有類似的實作？</li>
<li>有沒有經過驗證的第三方方案？</li>
</ul>
<p>這 5 分鐘的搜尋成本，遠低於自訂實作可能帶來的維護成本。</p>
<h3 id="原則四可觀測性是必要投資">原則四：可觀測性是必要投資</h3>
<p>看不見的問題成本最高。因為：</p>
<ul>
<li>你不知道它存在（發現成本高）</li>
<li>你不知道它影響多大（評估成本高）</li>
<li>你不知道它什麼時候開始的（追溯成本高）</li>
</ul>
<h3 id="原則五找到取捨的平衡點">原則五：找到取捨的平衡點</h3>
<p>很少有決策是「A 絕對比 B 好」。更多的情況是：</p>
<blockquote>
<p>A 在維度 X 上更好，B 在維度 Y 上更好。</p></blockquote>
<p>成本思維是在限制條件下找到<strong>總成本最低的方案</strong>。</p>
<h3 id="原則六失敗有成本預防是投資">原則六：失敗有成本，預防是投資</h3>
<p>每次失敗都消耗資源。適當的預驗證和防護措施是一種投資 &ndash; 用確定的小成本，避免不確定的大損失。</p>
<h2 id="自我檢查清單">自我檢查清單</h2>
<p>做技術決策時，問自己這些問題：</p>
<ul>
<li><input disabled="" type="checkbox"> 這個方案的維護成本是多少？（不只是開發成本）</li>
<li><input disabled="" type="checkbox"> 標準庫或現有程式碼中有沒有類似的解決方案？</li>
<li><input disabled="" type="checkbox"> 這段程式碼會被複製到其他地方嗎？（DRY 風險）</li>
<li><input disabled="" type="checkbox"> 如果這裡出了問題，我能多快發現？（可觀測性）</li>
<li><input disabled="" type="checkbox"> 這個任務失敗的成本是多少？需要預驗證嗎？</li>
<li><input disabled="" type="checkbox"> 頻率設計是否在精確度和成本之間取得平衡？</li>
</ul>
<h2 id="小結">小結</h2>
<p>成本思維是把時間軸拉長來做決策。</p>
<p>很多「快速」的決策，在長期看來是最昂貴的。而很多看似「多餘」的投資（可觀測性、共用模組、預驗證），在長期看來反而是成本最低的選擇。</p>
<p>軟體開發不只是寫程式碼 &ndash; 它是在有限資源下做出無數個取捨決策。理解每個決策的隱性成本，才能做出真正「划算」的選擇。</p>
<blockquote>
<p>最便宜的 bug 是那個從未被寫出來的 bug。</p></blockquote>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的</a> - 認知負擔也是一種「隱性成本」</li>
<li><a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事</a> - 好的命名降低閱讀成本</li>
<li><a href="/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔</a> - OCP 降低擴展成本</li>
<li><a href="/blog/python/07-refactoring/dry-principle/" data-link-title="DRY 原則與共用程式庫" data-link-desc="學習識別重複程式碼並建立共用模組，含模組演進與漸進遷移策略">DRY 原則與共用程式庫</a> - 重複程式碼的成本控制實戰</li>
<li><a href="/blog/python/05-error-testing/observability-design/" data-link-title="5.6 Hook 系統可觀測性設計" data-link-desc="日誌架構、錯誤可見性、健康監控：讓 44 個 Hook 的運行狀態透明可追蹤">Hook 系統可觀測性設計</a> - 可觀測性投資的詳細案例</li>
</ul>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>McConnell, S. (2004). &ldquo;Code Complete: A Practical Handbook of Software Construction&rdquo;</li>
<li>Forsgren, N., Humble, J., &amp; Kim, G. (2018). &ldquo;Accelerate: The Science of Lean Software and DevOps&rdquo;</li>
</ul>
]]></content:encoded></item><item><title>0.5 Go 和其他並發語言的差異</title><link>https://tarrragon.github.io/blog/go/00-philosophy/concurrency-language-position/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/concurrency-language-position/</guid><description>&lt;p>Go 在並發語言中的核心定位是「用較低語言複雜度寫出可部署、可維護的高併發服務」。現代語言大多能處理並發；Go 的特色在於 goroutine、channel、context、標準庫與單一 binary 共同形成一套服務工程模型。&lt;/p>
&lt;p>語言比較的核心判斷是「哪一種並發模型會讓目前服務更容易寫清楚、部署簡單、長期維護」。Java、C#、Rust、Node.js、Python async、Erlang/Elixir 都能處理並發；這一章要比較的是它們各自把並發、生命週期與服務交付放在哪一種工程模型裡。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>區分「能並發」和「適合某種並發服務」的差異&lt;/li>
&lt;li>看懂 Go 的 goroutine 模型和 thread pool、async/await、actor model 的工程差異&lt;/li>
&lt;li>判斷 Go 與 Java/C#、Rust、Node.js、Python async、Erlang/Elixir 的選型邊界&lt;/li>
&lt;li>用工作負載、團隊維護成本與部署形態來比較語言&lt;/li>
&lt;li>把語言比較轉回工程問題，形成可檢查的選型依據&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察現代語言大多有並發能力">【觀察】現代語言大多有並發能力&lt;/h2>
&lt;p>並發能力已經是現代後端語言的基本能力。Java 有 thread pool、virtual threads 與成熟框架；C# 有 Task 與 async/await；Rust 有 async runtime 與底層控制；Node.js 和 Python 有事件迴圈與 async 生態；Erlang/Elixir 有 actor 與 supervision tree。&lt;/p>
&lt;p>因此，Go 的選型問題應該聚焦在「哪一種並發模型符合目前工作負載」。更有用的問題是：&lt;/p>
&lt;ol>
&lt;li>這個服務主要是大量等待 I/O，還是大量 CPU 計算？&lt;/li>
&lt;li>團隊希望用同步風格寫流程，還是接受 async callback / async function 傳播？&lt;/li>
&lt;li>服務是否需要大量長生命週期工作單元？&lt;/li>
&lt;li>部署是否重視單一 binary、啟動速度與少量 runtime 依賴？&lt;/li>
&lt;li>團隊是否更重視語言簡單度、企業框架、底層控制或容錯模型？&lt;/li>
&lt;/ol>
&lt;p>這些問題會把語言比較轉成工程比較。語言本身只是工具，工作負載與團隊約束才是選型依據。&lt;/p>
&lt;h2 id="判讀go-的差異是服務工程模型">【判讀】Go 的差異是服務工程模型&lt;/h2>
&lt;p>Go 的並發模型把「工作單位」表達成 goroutine，把「取消與逾時」表達成 context，把「協調訊號」表達成 channel 或同步原語。這讓大量等待型工作可以長得像普通函式流程。&lt;/p>
&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">handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">request&lt;/span> &lt;span class="nx">Request&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"> 2&lt;/span>&lt;span class="cl"> &lt;span class="nx">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">request&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ID&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">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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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;fetch data: %w&amp;#34;&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"> 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="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">repository&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">result&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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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;save result: %w&amp;#34;&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"> 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="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>這段程式沒有展示 goroutine，但它已經承接 Go 並發服務的核心語意：每個 request 有自己的 context，外部 I/O 接受取消，錯誤沿著呼叫鏈回傳。當這段流程被 HTTP handler、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool&lt;/a> 或 &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;/p>
&lt;p>Go 的優勢通常出現在三個地方：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Go 的工程特性&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>並發工作單位&lt;/td>
 &lt;td>goroutine 成本低，適合大量等待型工作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生命週期控制&lt;/td>
 &lt;td>&lt;code>context&lt;/code> 讓 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>、cancel、request-scoped value 有共同傳遞方式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務交付&lt;/td>
 &lt;td>編譯成單一 binary，container、CLI、sidecar 與小型服務部署簡單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表只是索引。下面幾節會把 Go 放到不同語言模型旁邊比較，重點是辨識每種模型適合的服務形狀。&lt;/p>
&lt;h2 id="判讀go-vs-java--c輕量服務模型與成熟平台模型">【判讀】Go vs Java / C#：輕量服務模型與成熟平台模型&lt;/h2>
&lt;p>Java 與 C# 的核心優勢是成熟平台、企業框架、完整工具鏈與大型組織生態。當系統需要完整 ORM、生態整合、企業身份驗證、複雜業務框架、長期平台治理時，Java / C# 經常是穩定選擇。&lt;/p>
&lt;p>接近真實網路服務的例子包括：&lt;/p>
&lt;ul>
&lt;li>大型銀行或保險系統，需要完整交易、稽核、權限與企業整合&lt;/li>
&lt;li>企業內部 ERP、CRM、供應鏈系統，需要成熟框架與長期治理&lt;/li>
&lt;li>使用 Spring、ASP.NET、Entity Framework 等框架已經形成團隊標準的組織&lt;/li>
&lt;/ul>
&lt;p>Go 的差異在於服務模型更輕。當服務主要是 HTTP/gRPC API、background worker、gateway、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> server、CLI 或基礎設施元件時，Go 可以用較少框架建立清楚邊界。程式啟動、部署、容器化和交接通常也比較直接。&lt;/p>
&lt;p>判斷問題可以這樣問：這個系統的主要價值在成熟企業平台與框架整合，還是在小型服務、簡單部署、清楚並發生命週期？前者常偏向 Java/C# 生態，後者常讓 Go 更有吸引力。&lt;/p>
&lt;h2 id="判讀go-vs-rust服務工程與底層控制">【判讀】Go vs Rust：服務工程與底層控制&lt;/h2>
&lt;p>Rust 的核心優勢是記憶體安全、零成本抽象、所有權模型與底層控制。當系統需要精細控制記憶體、避免 GC pause、處理高效能底層元件或安全敏感邊界時，Rust 的能力很強。&lt;/p></description><content:encoded><![CDATA[<p>Go 在並發語言中的核心定位是「用較低語言複雜度寫出可部署、可維護的高併發服務」。現代語言大多能處理並發；Go 的特色在於 goroutine、channel、context、標準庫與單一 binary 共同形成一套服務工程模型。</p>
<p>語言比較的核心判斷是「哪一種並發模型會讓目前服務更容易寫清楚、部署簡單、長期維護」。Java、C#、Rust、Node.js、Python async、Erlang/Elixir 都能處理並發；這一章要比較的是它們各自把並發、生命週期與服務交付放在哪一種工程模型裡。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>區分「能並發」和「適合某種並發服務」的差異</li>
<li>看懂 Go 的 goroutine 模型和 thread pool、async/await、actor model 的工程差異</li>
<li>判斷 Go 與 Java/C#、Rust、Node.js、Python async、Erlang/Elixir 的選型邊界</li>
<li>用工作負載、團隊維護成本與部署形態來比較語言</li>
<li>把語言比較轉回工程問題，形成可檢查的選型依據</li>
</ol>
<hr>
<h2 id="觀察現代語言大多有並發能力">【觀察】現代語言大多有並發能力</h2>
<p>並發能力已經是現代後端語言的基本能力。Java 有 thread pool、virtual threads 與成熟框架；C# 有 Task 與 async/await；Rust 有 async runtime 與底層控制；Node.js 和 Python 有事件迴圈與 async 生態；Erlang/Elixir 有 actor 與 supervision tree。</p>
<p>因此，Go 的選型問題應該聚焦在「哪一種並發模型符合目前工作負載」。更有用的問題是：</p>
<ol>
<li>這個服務主要是大量等待 I/O，還是大量 CPU 計算？</li>
<li>團隊希望用同步風格寫流程，還是接受 async callback / async function 傳播？</li>
<li>服務是否需要大量長生命週期工作單元？</li>
<li>部署是否重視單一 binary、啟動速度與少量 runtime 依賴？</li>
<li>團隊是否更重視語言簡單度、企業框架、底層控制或容錯模型？</li>
</ol>
<p>這些問題會把語言比較轉成工程比較。語言本身只是工具，工作負載與團隊約束才是選型依據。</p>
<h2 id="判讀go-的差異是服務工程模型">【判讀】Go 的差異是服務工程模型</h2>
<p>Go 的並發模型把「工作單位」表達成 goroutine，把「取消與逾時」表達成 context，把「協調訊號」表達成 channel 或同步原語。這讓大量等待型工作可以長得像普通函式流程。</p>
<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">handle</span><span class="p">(</span><span class="nx">ctx</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">request</span> <span class="nx">Request</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">result</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">request</span><span class="p">.</span><span class="nx">ID</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">err</span> <span class="o">!=</span> <span class="kc">nil</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="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;fetch data: %w&#34;</span><span class="p">,</span> <span class="nx">err</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="nx">err</span> <span class="o">:=</span> <span class="nx">repository</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">result</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"> 8</span><span class="cl">        <span class="k">return</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;save result: %w&#34;</span><span class="p">,</span> <span class="nx">err</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="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>這段程式沒有展示 goroutine，但它已經承接 Go 並發服務的核心語意：每個 request 有自己的 context，外部 I/O 接受取消，錯誤沿著呼叫鏈回傳。當這段流程被 HTTP handler、<a href="/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool</a> 或 <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> 呼叫時，生命週期仍然清楚。</p>
<p>Go 的優勢通常出現在三個地方：</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Go 的工程特性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>並發工作單位</td>
          <td>goroutine 成本低，適合大量等待型工作</td>
      </tr>
      <tr>
          <td>生命週期控制</td>
          <td><code>context</code> 讓 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>、cancel、request-scoped value 有共同傳遞方式</td>
      </tr>
      <tr>
          <td>服務交付</td>
          <td>編譯成單一 binary，container、CLI、sidecar 與小型服務部署簡單</td>
      </tr>
  </tbody>
</table>
<p>這張表只是索引。下面幾節會把 Go 放到不同語言模型旁邊比較，重點是辨識每種模型適合的服務形狀。</p>
<h2 id="判讀go-vs-java--c輕量服務模型與成熟平台模型">【判讀】Go vs Java / C#：輕量服務模型與成熟平台模型</h2>
<p>Java 與 C# 的核心優勢是成熟平台、企業框架、完整工具鏈與大型組織生態。當系統需要完整 ORM、生態整合、企業身份驗證、複雜業務框架、長期平台治理時，Java / C# 經常是穩定選擇。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>大型銀行或保險系統，需要完整交易、稽核、權限與企業整合</li>
<li>企業內部 ERP、CRM、供應鏈系統，需要成熟框架與長期治理</li>
<li>使用 Spring、ASP.NET、Entity Framework 等框架已經形成團隊標準的組織</li>
</ul>
<p>Go 的差異在於服務模型更輕。當服務主要是 HTTP/gRPC API、background worker、gateway、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> server、CLI 或基礎設施元件時，Go 可以用較少框架建立清楚邊界。程式啟動、部署、容器化和交接通常也比較直接。</p>
<p>判斷問題可以這樣問：這個系統的主要價值在成熟企業平台與框架整合，還是在小型服務、簡單部署、清楚並發生命週期？前者常偏向 Java/C# 生態，後者常讓 Go 更有吸引力。</p>
<h2 id="判讀go-vs-rust服務工程與底層控制">【判讀】Go vs Rust：服務工程與底層控制</h2>
<p>Rust 的核心優勢是記憶體安全、零成本抽象、所有權模型與底層控制。當系統需要精細控制記憶體、避免 GC pause、處理高效能底層元件或安全敏感邊界時，Rust 的能力很強。</p>
<p>接近真實網路服務與系統元件的例子包括：</p>
<ul>
<li>高效能 proxy、資料處理引擎或邊緣運算元件</li>
<li>需要控制記憶體配置與延遲尖峰的低層服務</li>
<li>瀏覽器、資料庫、區塊鏈節點、嵌入式或安全敏感元件</li>
</ul>
<p>Go 的差異在於它把 GC、簡單型別、顯式錯誤處理和 goroutine 組成服務工程預設值。團隊通常可以更快建立 HTTP service、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> 或內部平台工具。Go 會讓你接受 runtime 管理記憶體，換取較低心智負擔與較快服務交付。</p>
<p>判斷問題可以這樣問：主要風險是記憶體控制與極致效能，還是服務生命週期、部署、可讀性與交付速度？前者常讓 Rust 更合理，後者常讓 Go 更直接。</p>
<h2 id="判讀go-vs-nodejs--python-async同步風格與事件迴圈模型">【判讀】Go vs Node.js / Python async：同步風格與事件迴圈模型</h2>
<p>Node.js 與 Python async 的核心優勢是事件迴圈模型、豐富應用生態與快速產品整合。當服務以 I/O 為主，且團隊已經在 JavaScript、TypeScript 或 Python 生態中累積大量工具，async/await 可以建立高產能工作流。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>以 Next.js、Remix、FastAPI、Django 或 Flask 為核心的產品服務</li>
<li>需要快速串接 SaaS API、資料處理腳本、內容管理與前端整合的系統</li>
<li>團隊主要技能集中在 JavaScript/TypeScript 或 Python 的新產品</li>
</ul>
<p>Go 的差異在於 goroutine 讓等待型流程看起來更接近普通同步程式。當一個 request 需要呼叫多個下游、寫入狀態、處理 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>、再把錯誤回傳，Go 通常能把控制流程維持在直線式函式中。多核心 CPU 使用、長時間 worker、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> 連線與 <a href="/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">shutdown</a> 流程也能用同一套 goroutine/context 模型處理。</p>
<p>判斷問題可以這樣問：主要價值在前端/資料/腳本生態和快速整合，還是在長時間服務、清楚生命週期與單一部署產物？前者常偏向 Node.js 或 Python async 生態，後者常讓 Go 更自然。</p>
<h2 id="判讀go-vs-erlang--elixir通用服務與-actor-容錯模型">【判讀】Go vs Erlang / Elixir：通用服務與 actor 容錯模型</h2>
<p>Erlang / Elixir 的核心優勢是 actor model、supervision tree、熱更新文化與分散式容錯思想。當系統需要大量獨立 actor、強調隔離、復原和訊息傳遞時，BEAM 生態有非常成熟的模型。</p>
<p>接近真實網路服務的例子包括：</p>
<ul>
<li>即時通訊與 presence 系統</li>
<li>大量獨立 session、room、process 的通訊服務</li>
<li>需要 supervision tree 管理故障復原的長時間系統</li>
</ul>
<p>Go 的差異在於它更像通用後端與基礎設施語言。你可以用 goroutine 和 channel 建立 actor-like 結構，但 Go 的標準模型更偏向明確組裝：handler、worker、repository、publisher、context cancellation、<a href="/blog/backend/knowledge-cards/graceful-shutdown/" data-link-title="Graceful Shutdown" data-link-desc="說明服務停止前如何排空流量、完成工作與保存狀態">graceful shutdown</a>。這讓 Go 在一般 API service、worker、CLI、gateway、sidecar、平台工具中更容易被多數後端團隊採用。</p>
<p>判斷問題可以這樣問：系統核心是否需要 actor supervision 與 fault-tolerant messaging 作為主要模型？如果答案是肯定的，Erlang / Elixir 值得認真評估；如果系統是一般後端服務與平台元件，Go 的採用門檻與部署模型通常更直接。</p>
<h2 id="策略用比較軸選語言">【策略】用比較軸選語言</h2>
<p>語言比較應該回到可觀察的工程條件。下面這張表可以當成選型索引：</p>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>更常見的候選方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大量 I/O、長連線、worker、簡單部署</td>
          <td>Go</td>
      </tr>
      <tr>
          <td>大型企業框架、成熟平台治理、完整商業系統生態</td>
          <td>Java / C#</td>
      </tr>
      <tr>
          <td>記憶體控制、底層效能、安全敏感元件</td>
          <td>Rust</td>
      </tr>
      <tr>
          <td>前端整合、SaaS 串接、資料腳本、產品快速整合</td>
          <td>Node.js / Python async</td>
      </tr>
      <tr>
          <td>actor、supervision、分散式容錯模型</td>
          <td>Erlang / Elixir</td>
      </tr>
  </tbody>
</table>
<p>這張表的用途是建立第一輪比較方向。實際選型還要看團隊經驗、既有系統、部署平台、觀測工具、人才供給與維護週期。</p>
<p>若一個服務需要同時支援 HTTP API、背景 worker、<a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">Webhook</a> callback、<a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> 推送與簡單容器部署，Go 的整體組合很強。若一個產品主要依賴企業框架、動態產品流程、底層控制或 actor 容錯，其他語言可能更貼近主要問題。</p>
<h2 id="執行把語言比較寫成工程判斷">【執行】把語言比較寫成工程判斷</h2>
<p>好的語言比較結論應該包含工作負載、主要風險與取捨。語言名稱是結論，前面的工程條件才是判斷依據。</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">這個服務主要是 [webhook](/backend/knowledge-cards/webhook/) receiver、[queue](/backend/knowledge-cards/queue/) [consumer](/backend/knowledge-cards/consumer/) 與 [WebSocket](/backend/knowledge-cards/websocket/) 推送。
</span></span><span class="line"><span class="ln">2</span><span class="cl">主要風險是大量 I/O、[timeout](/backend/knowledge-cards/timeout/)、[backpressure](/go/backend/knowledge-cards/backpressure/) 與 [graceful shutdown](/backend/knowledge-cards/graceful-shutdown/)。
</span></span><span class="line"><span class="ln">3</span><span class="cl">Go 的 goroutine/context 模型和單一 binary 部署符合這些條件，所以 Go 是好候選。</span></span></code></pre></div><p>也可以這樣寫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">這個系統主要是企業內部資料管理、權限、報表與工作流。
</span></span><span class="line"><span class="ln">2</span><span class="cl">主要風險是業務規則完整性、框架生態、資料庫整合與長期平台治理。
</span></span><span class="line"><span class="ln">3</span><span class="cl">Java / C# 生態可能比 Go 更貼近主問題。</span></span></code></pre></div><p>語言選型的核心輸出是「為什麼這個工作負載適合某個模型」。當比較句能說清楚工作負載、風險與取捨，團隊未來也能在條件改變時重新評估。</p>
<h2 id="和本模組的關係">和本模組的關係</h2>
<p>這一章承接 <a href="/blog/go/00-philosophy/selecting-go/" data-link-title="0.4 什麼時候選 Go" data-link-desc="用選型條件判斷 Go 是否適合高併發服務、背景工作與長連線場景">0.4 什麼時候選 Go</a>。0.4 先判斷工作場景是否適合 Go；0.5 再把 Go 放到其他並發語言旁邊，理解它的工程定位。</p>
<p>讀完本章後，可以回到：</p>
<ul>
<li><a href="/blog/go/00-philosophy/simplicity/" data-link-title="0.1 Go 的簡單哲學與認知負擔" data-link-desc="理解 Go 為什麼偏好顯式、直線流程與少量語法">Go 的簡單哲學與認知負擔</a></li>
<li><a href="/blog/go/00-philosophy/composition/" data-link-title="0.2 組合優先：小介面與明確依賴" data-link-desc="用小介面與 struct 組合取代大型繼承結構">組合優先：小介面與明確依賴</a></li>
<li><a href="/blog/go/04-concurrency/" data-link-title="模組四：並發模型" data-link-desc="從 goroutine、channel、select 與 RWMutex 理解 Go 並發模型">Go 並發模型</a></li>
</ul>
]]></content:encoded></item><item><title>模組零：Go 選型與設計哲學</title><link>https://tarrragon.github.io/blog/go/00-philosophy/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/00-philosophy/</guid><description>&lt;p>Go 的語言設計刻意保守：語法少、抽象少、控制流程直接。這種取捨把維護長期程式時最常見的成本前置處理：閱讀成本、除錯成本、交接成本。第零章先回答「什麼情境值得選 Go」，再說明為什麼 Go 會長成現在這個樣子。&lt;/p>
&lt;h2 id="選型判斷">選型判斷&lt;/h2>
&lt;p>第零章先處理一個問題：你的工作場景是否適合 Go。若工作型態以高併發 I/O、長連線、背景處理或事件驅動為主，系統又需要清楚邊界、穩定執行與一致的工程流程，那 Go 通常值得優先考慮。若情境更偏重框架生態、動態行為或大量既有業務模板，下一步應先比較其他語言或框架。完成基本選型後，本模組也會把 Go 放到其他並發語言旁邊，說明它在服務工程上的定位。&lt;/p>
&lt;p>這種判斷的用途是幫後面的章節建立正確順序：先知道何時值得用 Go，再去理解它為什麼長成現在這個樣子。&lt;/p>
&lt;h2 id="章節列表">章節列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>關鍵收穫&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/simplicity/" data-link-title="0.1 Go 的簡單哲學與認知負擔" data-link-desc="理解 Go 為什麼偏好顯式、直線流程與少量語法">0.1&lt;/a>&lt;/td>
 &lt;td>Go 的簡單哲學與認知負擔&lt;/td>
 &lt;td>理解 Go 為什麼適合可讀、可交接的服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/composition/" data-link-title="0.2 組合優先：小介面與明確依賴" data-link-desc="用小介面與 struct 組合取代大型繼承結構">0.2&lt;/a>&lt;/td>
 &lt;td>組合優先：小介面與明確依賴&lt;/td>
 &lt;td>用 interface 表達行為能力與依賴邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/error-thinking/" data-link-title="0.3 錯誤處理：把失敗路徑寫出來" data-link-desc="理解 Go 顯式錯誤處理在服務維護中的價值">0.3&lt;/a>&lt;/td>
 &lt;td>錯誤處理：把失敗路徑寫出來&lt;/td>
 &lt;td>理解 &lt;code>if err != nil&lt;/code> 的維護價值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/selecting-go/" data-link-title="0.4 什麼時候選 Go" data-link-desc="用選型條件判斷 Go 是否適合高併發服務、背景工作與長連線場景">0.4&lt;/a>&lt;/td>
 &lt;td>什麼時候選 Go&lt;/td>
 &lt;td>用選型條件判斷 Go 是否適合這類服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/go/00-philosophy/concurrency-language-position/" data-link-title="0.5 Go 和其他並發語言的差異" data-link-desc="比較 Go、Java、C#、Rust、Node.js、Python async、Erlang/Elixir 在並發服務中的工程定位">0.5&lt;/a>&lt;/td>
 &lt;td>Go 和其他並發語言的差異&lt;/td>
 &lt;td>用工作負載比較 Go、Java/C#、Rust、Node.js、Python async、Erlang/Elixir&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="本模組使用的範例主題">本模組使用的範例主題&lt;/h2>
&lt;ul>
&lt;li>用選型條件判斷高併發服務、worker 與長連線場景是否適合 Go&lt;/li>
&lt;li>用工作負載比較 Go 與其他並發語言的工程定位&lt;/li>
&lt;li>應用啟動流程的顯式依賴組裝&lt;/li>
&lt;li>用 struct 與方法管理共享狀態&lt;/li>
&lt;li>用早期返回處理 HTTP 錯誤路徑&lt;/li>
&lt;/ul>
&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>Go 的語言設計刻意保守：語法少、抽象少、控制流程直接。這種取捨把維護長期程式時最常見的成本前置處理：閱讀成本、除錯成本、交接成本。第零章先回答「什麼情境值得選 Go」，再說明為什麼 Go 會長成現在這個樣子。</p>
<h2 id="選型判斷">選型判斷</h2>
<p>第零章先處理一個問題：你的工作場景是否適合 Go。若工作型態以高併發 I/O、長連線、背景處理或事件驅動為主，系統又需要清楚邊界、穩定執行與一致的工程流程，那 Go 通常值得優先考慮。若情境更偏重框架生態、動態行為或大量既有業務模板，下一步應先比較其他語言或框架。完成基本選型後，本模組也會把 Go 放到其他並發語言旁邊，說明它在服務工程上的定位。</p>
<p>這種判斷的用途是幫後面的章節建立正確順序：先知道何時值得用 Go，再去理解它為什麼長成現在這個樣子。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/go/00-philosophy/simplicity/" data-link-title="0.1 Go 的簡單哲學與認知負擔" data-link-desc="理解 Go 為什麼偏好顯式、直線流程與少量語法">0.1</a></td>
          <td>Go 的簡單哲學與認知負擔</td>
          <td>理解 Go 為什麼適合可讀、可交接的服務</td>
      </tr>
      <tr>
          <td><a href="/blog/go/00-philosophy/composition/" data-link-title="0.2 組合優先：小介面與明確依賴" data-link-desc="用小介面與 struct 組合取代大型繼承結構">0.2</a></td>
          <td>組合優先：小介面與明確依賴</td>
          <td>用 interface 表達行為能力與依賴邊界</td>
      </tr>
      <tr>
          <td><a href="/blog/go/00-philosophy/error-thinking/" data-link-title="0.3 錯誤處理：把失敗路徑寫出來" data-link-desc="理解 Go 顯式錯誤處理在服務維護中的價值">0.3</a></td>
          <td>錯誤處理：把失敗路徑寫出來</td>
          <td>理解 <code>if err != nil</code> 的維護價值</td>
      </tr>
      <tr>
          <td><a href="/blog/go/00-philosophy/selecting-go/" data-link-title="0.4 什麼時候選 Go" data-link-desc="用選型條件判斷 Go 是否適合高併發服務、背景工作與長連線場景">0.4</a></td>
          <td>什麼時候選 Go</td>
          <td>用選型條件判斷 Go 是否適合這類服務</td>
      </tr>
      <tr>
          <td><a href="/blog/go/00-philosophy/concurrency-language-position/" data-link-title="0.5 Go 和其他並發語言的差異" data-link-desc="比較 Go、Java、C#、Rust、Node.js、Python async、Erlang/Elixir 在並發服務中的工程定位">0.5</a></td>
          <td>Go 和其他並發語言的差異</td>
          <td>用工作負載比較 Go、Java/C#、Rust、Node.js、Python async、Erlang/Elixir</td>
      </tr>
  </tbody>
</table>
<h2 id="本模組使用的範例主題">本模組使用的範例主題</h2>
<ul>
<li>用選型條件判斷高併發服務、worker 與長連線場景是否適合 Go</li>
<li>用工作負載比較 Go 與其他並發語言的工程定位</li>
<li>應用啟動流程的顯式依賴組裝</li>
<li>用 struct 與方法管理共享狀態</li>
<li>用早期返回處理 HTTP 錯誤路徑</li>
</ul>
<h2 id="預備知識">預備知識</h2>
<ul>
<li>基本程式設計概念</li>
<li>對任一程式語言有基礎了解</li>
</ul>
<h2 id="學習時間">學習時間</h2>
<p>預計 45-60 分鐘</p>
]]></content:encoded></item><item><title>模組零：設計哲學（序章）</title><link>https://tarrragon.github.io/blog/python/00-philosophy/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/</guid><description>&lt;p>在深入學習 Python 技術細節之前，讓我們先建立一個統一的視角：&lt;strong>所有程式碼設計原則的最終目的都是「降低閱讀者的認知負擔」&lt;/strong>。&lt;/p>
&lt;h2 id="為什麼需要這個序章">為什麼需要這個序章？&lt;/h2>
&lt;p>你可能聽過很多設計原則：DRY、SOLID、Clean Code、重構技巧&amp;hellip;但這些原則為什麼存在？它們的共同目標是什麼？&lt;/p>
&lt;p>本模組將回答這個根本問題，並提供一個統一的框架來理解所有設計決策。&lt;/p>
&lt;h2 id="核心論點">核心論點&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">程式碼的品質 ≠ 優美
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">程式碼的品質 = 易讀&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>無法讀懂的程式碼沒人會讀，更不用說重構或除錯。所有的設計原則都是為了讓程式碼更容易被人類理解。&lt;/p>
&lt;h2 id="章節內容">章節內容&lt;/h2>
&lt;h3 id="認知負擔程式碼設計的核心目的">&lt;a href="https://tarrragon.github.io/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>什麼是認知負擔？&lt;/li>
&lt;li>為什麼「可讀」比「優美」更重要&lt;/li>
&lt;li>認知負擔的來源分析&lt;/li>
&lt;li>降低認知負擔的基本原則&lt;/li>
&lt;/ul>
&lt;h3 id="命名的藝術讓程式碼說故事">&lt;a href="https://tarrragon.github.io/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>程式碼是一個故事&lt;/li>
&lt;li>變數命名的藝術&lt;/li>
&lt;li>函式命名的藝術&lt;/li>
&lt;li>命名與認知負擔的關係&lt;/li>
&lt;/ul>
&lt;h3 id="開放封閉原則與認知負擔">&lt;a href="https://tarrragon.github.io/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>OCP 的傳統解釋&lt;/li>
&lt;li>OCP 的認知負擔視角&lt;/li>
&lt;li>單一職責原則的本質&lt;/li>
&lt;li>實際案例分析&lt;/li>
&lt;/ul>
&lt;h3 id="成本思維軟體開發的隱性代價">&lt;a href="https://tarrragon.github.io/blog/python/00-philosophy/cost-thinking/" data-link-title="成本思維：軟體開發的隱性代價" data-link-desc="每個技術決策都有成本，學會識別和評估隱性代價">成本思維：軟體開發的隱性代價&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>顯性成本 vs 隱性成本&lt;/li>
&lt;li>重新造輪子的真實成本&lt;/li>
&lt;li>重複程式碼的累積成本&lt;/li>
&lt;li>可觀測性的投資回報&lt;/li>
&lt;li>系統設計中的頻率取捨&lt;/li>
&lt;li>成本思維的核心原則&lt;/li>
&lt;/ul>
&lt;h2 id="學習目標">學習目標&lt;/h2>
&lt;p>完成本模組後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>用「降低認知負擔」的統一視角理解所有設計原則&lt;/li>
&lt;li>在命名時考慮閱讀者的認知負擔&lt;/li>
&lt;li>用認知負擔來評估設計決策的好壞&lt;/li>
&lt;li>理解為什麼某些程式碼「感覺不對」&lt;/li>
&lt;li>用「總成本」而非「開發成本」來評估技術決策&lt;/li>
&lt;/ol>
&lt;h2 id="與其他模組的關係">與其他模組的關係&lt;/h2>
&lt;p>本模組是後續所有模組的理論基礎：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>模組一到五&lt;/strong>：學習技術細節時，思考這些技術如何降低認知負擔&lt;/li>
&lt;li>&lt;strong>模組六&lt;/strong>：實戰時，用認知負擔來指導設計決策&lt;/li>
&lt;li>&lt;strong>模組七&lt;/strong>：重構時，以降低認知負擔為目標&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>本模組基於專案實際經驗和軟體工程最佳實踐整理&lt;/p></description><content:encoded><![CDATA[<p>在深入學習 Python 技術細節之前，讓我們先建立一個統一的視角：<strong>所有程式碼設計原則的最終目的都是「降低閱讀者的認知負擔」</strong>。</p>
<h2 id="為什麼需要這個序章">為什麼需要這個序章？</h2>
<p>你可能聽過很多設計原則：DRY、SOLID、Clean Code、重構技巧&hellip;但這些原則為什麼存在？它們的共同目標是什麼？</p>
<p>本模組將回答這個根本問題，並提供一個統一的框架來理解所有設計決策。</p>
<h2 id="核心論點">核心論點</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">程式碼的品質 ≠ 優美
</span></span><span class="line"><span class="ln">2</span><span class="cl">程式碼的品質 = 易讀</span></span></code></pre></div><p>無法讀懂的程式碼沒人會讀，更不用說重構或除錯。所有的設計原則都是為了讓程式碼更容易被人類理解。</p>
<h2 id="章節內容">章節內容</h2>
<h3 id="認知負擔程式碼設計的核心目的"><a href="/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的</a></h3>
<ul>
<li>什麼是認知負擔？</li>
<li>為什麼「可讀」比「優美」更重要</li>
<li>認知負擔的來源分析</li>
<li>降低認知負擔的基本原則</li>
</ul>
<h3 id="命名的藝術讓程式碼說故事"><a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事</a></h3>
<ul>
<li>程式碼是一個故事</li>
<li>變數命名的藝術</li>
<li>函式命名的藝術</li>
<li>命名與認知負擔的關係</li>
</ul>
<h3 id="開放封閉原則與認知負擔"><a href="/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔</a></h3>
<ul>
<li>OCP 的傳統解釋</li>
<li>OCP 的認知負擔視角</li>
<li>單一職責原則的本質</li>
<li>實際案例分析</li>
</ul>
<h3 id="成本思維軟體開發的隱性代價"><a href="/blog/python/00-philosophy/cost-thinking/" data-link-title="成本思維：軟體開發的隱性代價" data-link-desc="每個技術決策都有成本，學會識別和評估隱性代價">成本思維：軟體開發的隱性代價</a></h3>
<ul>
<li>顯性成本 vs 隱性成本</li>
<li>重新造輪子的真實成本</li>
<li>重複程式碼的累積成本</li>
<li>可觀測性的投資回報</li>
<li>系統設計中的頻率取捨</li>
<li>成本思維的核心原則</li>
</ul>
<h2 id="學習目標">學習目標</h2>
<p>完成本模組後，你將能夠：</p>
<ol>
<li>用「降低認知負擔」的統一視角理解所有設計原則</li>
<li>在命名時考慮閱讀者的認知負擔</li>
<li>用認知負擔來評估設計決策的好壞</li>
<li>理解為什麼某些程式碼「感覺不對」</li>
<li>用「總成本」而非「開發成本」來評估技術決策</li>
</ol>
<h2 id="與其他模組的關係">與其他模組的關係</h2>
<p>本模組是後續所有模組的理論基礎：</p>
<ul>
<li><strong>模組一到五</strong>：學習技術細節時，思考這些技術如何降低認知負擔</li>
<li><strong>模組六</strong>：實戰時，用認知負擔來指導設計決策</li>
<li><strong>模組七</strong>：重構時，以降低認知負擔為目標</li>
</ul>
<hr>
<p>本模組基於專案實際經驗和軟體工程最佳實踐整理</p>
]]></content:encoded></item></channel></rss>