<?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>Cognitive-Load on Tarragon</title><link>https://tarrragon.github.io/blog/tags/cognitive-load/</link><description>Recent content in Cognitive-Load on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/cognitive-load/index.xml" rel="self" type="application/rss+xml"/><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>命名的藝術：讓程式碼說故事</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>開放封閉原則與認知負擔</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>重構的動機與策略</title><link>https://tarrragon.github.io/blog/python/07-refactoring/refactoring-strategy/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/07-refactoring/refactoring-strategy/</guid><description>&lt;p>你有沒有修完一個 Bug，一週後發現另一個地方有完全一樣的問題？或者改了一個函式的行為，卻不確定還有哪些地方依賴它？這些情境的根源往往是程式碼結構讓問題難以被看見。&lt;/p>
&lt;p>重構是改變程式碼的內部結構，而不改變其外部行為。聽起來簡單，但實務上最困難的問題不是「怎麼改」，而是「為什麼改」和「什麼順序改」。&lt;/p>
&lt;p>本章從 Hook 系統兩次大規模重構（v0.28.0 和 v0.31.0）的經驗出發，討論重構的動機判斷和階段分解策略。Hook 系統是一個由數十個 Python 腳本組成的自動化系統，負責程式碼品質檢查、流程驗證和開發規範執行。&lt;/p>
&lt;h2 id="為什麼要重構">為什麼要重構&lt;/h2>
&lt;h3 id="認知負擔超載">認知負擔超載&lt;/h3>
&lt;p>重構的第一個訊號是：&lt;strong>讀程式碼時，你需要同時記住太多東西。&lt;/strong>&lt;/p>
&lt;p>v0.28.0 重構前，&lt;code>task-dispatch-readiness-check.py&lt;/code> 有 858 行。閱讀這個檔案時，你需要同時追蹤：&lt;/p>
&lt;ul>
&lt;li>15 個代理人的名稱和觸發條件&lt;/li>
&lt;li>Git 分支操作的 &lt;code>subprocess&lt;/code> 細節&lt;/li>
&lt;li>Worktree 路徑解析邏輯&lt;/li>
&lt;li>重複出現的工具函式（同一個 &lt;code>run_git_command&lt;/code> 在多個檔案中各自定義一次）&lt;/li>
&lt;/ul>
&lt;p>心理學家 George Miller 的研究指出，人類的工作記憶一次只能處理 7 加減 2 個項目。858 行的檔案遠超這個限制。你不可能在腦中同時維護這麼多上下文來理解程式碼的行為。&lt;/p>
&lt;p>我們可以用一個簡單的公式量化這個問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">認知負擔指數 = 變數數 + 分支數 + 巢狀深度 + 依賴數&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指數&lt;/th>
 &lt;th>評估&lt;/th>
 &lt;th>行動&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1-5&lt;/td>
 &lt;td>優良&lt;/td>
 &lt;td>維持&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>6-10&lt;/td>
 &lt;td>可接受&lt;/td>
 &lt;td>考慮最佳化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>11-15&lt;/td>
 &lt;td>需重構&lt;/td>
 &lt;td>排入計畫&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&amp;gt; 15&lt;/td>
 &lt;td>必須重構&lt;/td>
 &lt;td>立即處理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>v0.28.0 重構前的 &lt;code>task-dispatch-readiness-check.py&lt;/code>，光是頂層函式就有 23 個，模組級變數超過 10 個。認知負擔指數遠超 15，屬於「必須重構」等級。&lt;/p>
&lt;h3 id="不重構的代價">不重構的代價&lt;/h3>
&lt;p>「能動就不要碰」是常見的想法，但不重構的代價會隨時間累積。&lt;/p>
&lt;p>v0.31.0 的 W24 開發週期提供了一個具體案例。任務是統一 16 個 Hook 檔案的 logger 初始化風格——看起來是一個簡單的機械性修改。但因為缺乏共用模組和清晰的模組邊界，修改引發了一個作用域問題（將全域變數移入函式後，其他引用該變數的函式失去存取權限），導致 7 個 Hook 靜默失敗，影響 41 個函式。更糟的是，這個問題至少持續了 2 個 session 才被發現。&lt;/p>
&lt;blockquote>
&lt;p>完整的作用域迴歸分析參見&lt;a href="https://tarrragon.github.io/blog/python/07-refactoring/scope-regression/" data-link-title="作用域迴歸案例研究" data-link-desc="從 IMP-003 事件學習 Python 變數作用域的陷阱">作用域迴歸案例研究&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>靜默失敗比直接報錯更危險。錯誤被頂層的 &lt;code>try/except&lt;/code> 吞掉，只寫入日誌檔案，而沒有人在看日誌。如果重構前就有清晰的模組邊界和完整的測試覆蓋，這個問題可以在修改當下就被偵測到。&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在專案根目錄執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">grep -h &lt;span class="s2">&amp;#34;^def &amp;#34;&lt;/span> .claude/hooks/*.py &lt;span class="p">|&lt;/span> sort &lt;span class="p">|&lt;/span> uniq -c &lt;span class="p">|&lt;/span> sort -rn &lt;span class="p">|&lt;/span> head -5&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>v0.28.0 重構前的結果顯示，&lt;code>run_git_command&lt;/code> 在多個檔案中各有一份定義。這意味著：&lt;/p>
&lt;ul>
&lt;li>修復一個 Bug 要改 4 個地方&lt;/li>
&lt;li>漏改任何一個就會產生行為不一致&lt;/li>
&lt;li>新增 Hook 時需要再複製一份&lt;/li>
&lt;/ul>
&lt;p>重複程式碼的總行數約 415 行。這 415 行不只是浪費空間——它們是 415 行的維護風險。&lt;/p>
&lt;h3 id="判斷三問">判斷三問&lt;/h3>
&lt;p>在決定是否重構之前，問自己三個問題：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>讀這段程式碼時，我需要同時記住多少東西？&lt;/strong> 超過 7 個就是警訊。&lt;/li>
&lt;li>&lt;strong>如果要修改一個行為，我需要改幾個地方？&lt;/strong> 超過 1 個就有重複的問題。&lt;/li>
&lt;li>&lt;strong>新人加入團隊後，需要多久才能理解這段程式碼？&lt;/strong> 如果答案是「需要有人口頭解釋」，那就是程式碼本身不夠清楚。&lt;/li>
&lt;/ol>
&lt;p>如果三個問題的答案都指向問題，那就該動手了。&lt;/p>
&lt;h2 id="何時不應該重構">何時不應該重構&lt;/h2>
&lt;p>不是所有情況都適合重構。以下三個時機，重構通常會帶來更多問題。&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">if&lt;/span> &lt;span class="nb">len&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 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">3&lt;/span>&lt;span class="cl"> &lt;span class="n">branches&lt;/span> &lt;span class="o">=&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 class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="n">branches&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">branches&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">b&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">5&lt;/span>&lt;span class="cl"> &lt;span class="n">protected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">branches&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;master&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;p>&lt;strong>原則&lt;/strong>：先寫測試，再重構。如果時間只夠做一件事，選擇寫測試。&lt;/p>
&lt;h3 id="修-bug-時順手重構">修 Bug 時順手重構&lt;/h3>
&lt;p>修 Bug 和重構是兩件不同的事。混在一起做會產生兩個問題：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>難以驗證&lt;/strong>：Bug 修好了嗎？還是被重構掩蓋了？&lt;/li>
&lt;li>&lt;strong>難以回溯&lt;/strong>：如果重構引入了新問題，&lt;code>git bisect&lt;/code> 無法區分哪些變更是修 Bug、哪些是重構&lt;/li>
&lt;/ol>





&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">commit: &amp;#34;修復 #42 並重構 git_utils&amp;#34; ← 兩件事混在一個 commit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"># 正確的工作流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">commit: &amp;#34;修復 #42：branch 名稱解析的空字串處理&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">commit: &amp;#34;重構 git_utils：抽取 parse_branch_name 函式&amp;#34;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>原則&lt;/strong>：先修好 Bug 並提交，確認測試通過，然後再開始重構。&lt;/p>
&lt;h3 id="時間壓力下">時間壓力下&lt;/h3>
&lt;p>v0.31.0 W24 的 logger 統一修改就是一個教訓——在時間壓力下跳過了跨函式引用的完整驗證，結果 7 個 Hook 靜默失敗，修復花費的時間遠超原本省下的。&lt;/p></description><content:encoded><![CDATA[<p>你有沒有修完一個 Bug，一週後發現另一個地方有完全一樣的問題？或者改了一個函式的行為，卻不確定還有哪些地方依賴它？這些情境的根源往往是程式碼結構讓問題難以被看見。</p>
<p>重構是改變程式碼的內部結構，而不改變其外部行為。聽起來簡單，但實務上最困難的問題不是「怎麼改」，而是「為什麼改」和「什麼順序改」。</p>
<p>本章從 Hook 系統兩次大規模重構（v0.28.0 和 v0.31.0）的經驗出發，討論重構的動機判斷和階段分解策略。Hook 系統是一個由數十個 Python 腳本組成的自動化系統，負責程式碼品質檢查、流程驗證和開發規範執行。</p>
<h2 id="為什麼要重構">為什麼要重構</h2>
<h3 id="認知負擔超載">認知負擔超載</h3>
<p>重構的第一個訊號是：<strong>讀程式碼時，你需要同時記住太多東西。</strong></p>
<p>v0.28.0 重構前，<code>task-dispatch-readiness-check.py</code> 有 858 行。閱讀這個檔案時，你需要同時追蹤：</p>
<ul>
<li>15 個代理人的名稱和觸發條件</li>
<li>Git 分支操作的 <code>subprocess</code> 細節</li>
<li>Worktree 路徑解析邏輯</li>
<li>重複出現的工具函式（同一個 <code>run_git_command</code> 在多個檔案中各自定義一次）</li>
</ul>
<p>心理學家 George Miller 的研究指出，人類的工作記憶一次只能處理 7 加減 2 個項目。858 行的檔案遠超這個限制。你不可能在腦中同時維護這麼多上下文來理解程式碼的行為。</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">認知負擔指數 = 變數數 + 分支數 + 巢狀深度 + 依賴數</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>指數</th>
          <th>評估</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1-5</td>
          <td>優良</td>
          <td>維持</td>
      </tr>
      <tr>
          <td>6-10</td>
          <td>可接受</td>
          <td>考慮最佳化</td>
      </tr>
      <tr>
          <td>11-15</td>
          <td>需重構</td>
          <td>排入計畫</td>
      </tr>
      <tr>
          <td>&gt; 15</td>
          <td>必須重構</td>
          <td>立即處理</td>
      </tr>
  </tbody>
</table>
<p>v0.28.0 重構前的 <code>task-dispatch-readiness-check.py</code>，光是頂層函式就有 23 個，模組級變數超過 10 個。認知負擔指數遠超 15，屬於「必須重構」等級。</p>
<h3 id="不重構的代價">不重構的代價</h3>
<p>「能動就不要碰」是常見的想法，但不重構的代價會隨時間累積。</p>
<p>v0.31.0 的 W24 開發週期提供了一個具體案例。任務是統一 16 個 Hook 檔案的 logger 初始化風格——看起來是一個簡單的機械性修改。但因為缺乏共用模組和清晰的模組邊界，修改引發了一個作用域問題（將全域變數移入函式後，其他引用該變數的函式失去存取權限），導致 7 個 Hook 靜默失敗，影響 41 個函式。更糟的是，這個問題至少持續了 2 個 session 才被發現。</p>
<blockquote>
<p>完整的作用域迴歸分析參見<a href="/blog/python/07-refactoring/scope-regression/" data-link-title="作用域迴歸案例研究" data-link-desc="從 IMP-003 事件學習 Python 變數作用域的陷阱">作用域迴歸案例研究</a>。</p></blockquote>
<p>靜默失敗比直接報錯更危險。錯誤被頂層的 <code>try/except</code> 吞掉，只寫入日誌檔案，而沒有人在看日誌。如果重構前就有清晰的模組邊界和完整的測試覆蓋，這個問題可以在修改當下就被偵測到。</p>
<h3 id="重複程式碼的連鎖效應">重複程式碼的連鎖效應</h3>
<p>另一個推動重構的因素是重複。我們用一行指令就能量化問題的嚴重程度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在專案根目錄執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">grep -h <span class="s2">&#34;^def &#34;</span> .claude/hooks/*.py <span class="p">|</span> sort <span class="p">|</span> uniq -c <span class="p">|</span> sort -rn <span class="p">|</span> head -5</span></span></code></pre></div><p>v0.28.0 重構前的結果顯示，<code>run_git_command</code> 在多個檔案中各有一份定義。這意味著：</p>
<ul>
<li>修復一個 Bug 要改 4 個地方</li>
<li>漏改任何一個就會產生行為不一致</li>
<li>新增 Hook 時需要再複製一份</li>
</ul>
<p>重複程式碼的總行數約 415 行。這 415 行不只是浪費空間——它們是 415 行的維護風險。</p>
<h3 id="判斷三問">判斷三問</h3>
<p>在決定是否重構之前，問自己三個問題：</p>
<ol>
<li><strong>讀這段程式碼時，我需要同時記住多少東西？</strong> 超過 7 個就是警訊。</li>
<li><strong>如果要修改一個行為，我需要改幾個地方？</strong> 超過 1 個就有重複的問題。</li>
<li><strong>新人加入團隊後，需要多久才能理解這段程式碼？</strong> 如果答案是「需要有人口頭解釋」，那就是程式碼本身不夠清楚。</li>
</ol>
<p>如果三個問題的答案都指向問題，那就該動手了。</p>
<h2 id="何時不應該重構">何時不應該重構</h2>
<p>不是所有情況都適合重構。以下三個時機，重構通常會帶來更多問題。</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">if</span> <span class="nb">len</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 class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">branches</span> <span class="o">=</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 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">4</span><span class="cl">    <span class="n">branches</span> <span class="o">=</span> <span class="p">[</span><span class="n">b</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">branches</span> <span class="k">if</span> <span class="n">b</span><span class="o">.</span><span class="n">strip</span><span class="p">()]</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">protected</span> <span class="o">=</span> <span class="p">[</span><span class="n">b</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">branches</span> <span class="k">if</span> <span class="n">b</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;main&#34;</span><span class="p">,</span> <span class="s2">&#34;master&#34;</span><span class="p">]]</span></span></span></code></pre></div><p>這段邏輯涉及空字串處理、換行符分割、空白清理。如果沒有測試覆蓋這些邊界情況，抽取函式時很容易改變行為而不自知。</p>
<p><strong>原則</strong>：先寫測試，再重構。如果時間只夠做一件事，選擇寫測試。</p>
<h3 id="修-bug-時順手重構">修 Bug 時順手重構</h3>
<p>修 Bug 和重構是兩件不同的事。混在一起做會產生兩個問題：</p>
<ol>
<li><strong>難以驗證</strong>：Bug 修好了嗎？還是被重構掩蓋了？</li>
<li><strong>難以回溯</strong>：如果重構引入了新問題，<code>git bisect</code> 無法區分哪些變更是修 Bug、哪些是重構</li>
</ol>





<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">commit: &#34;修復 #42 並重構 git_utils&#34;  ← 兩件事混在一個 commit
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"># 正確的工作流程
</span></span><span class="line"><span class="ln">5</span><span class="cl">commit: &#34;修復 #42：branch 名稱解析的空字串處理&#34;
</span></span><span class="line"><span class="ln">6</span><span class="cl">commit: &#34;重構 git_utils：抽取 parse_branch_name 函式&#34;</span></span></code></pre></div><p><strong>原則</strong>：先修好 Bug 並提交，確認測試通過，然後再開始重構。</p>
<h3 id="時間壓力下">時間壓力下</h3>
<p>v0.31.0 W24 的 logger 統一修改就是一個教訓——在時間壓力下跳過了跨函式引用的完整驗證，結果 7 個 Hook 靜默失敗，修復花費的時間遠超原本省下的。</p>
<p>重構需要完整的注意力。趕進度時進行重構，容易在壓力下跳過驗證步驟（「測試之後再補」），反而製造更多技術債務。</p>
<p><strong>原則</strong>：記錄下需要重構的地方，排入後續計畫。不要在時間壓力下動手。</p>
<h3 id="判斷清單">判斷清單</h3>
<p>把上述三個情境整理成一個快速檢查清單：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>是</th>
          <th>否</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>有測試覆蓋修改區域？</td>
          <td>可以繼續</td>
          <td>先寫測試</td>
      </tr>
      <tr>
          <td>修改範圍只有重構？</td>
          <td>可以繼續</td>
          <td>先分開 Bug 修復和重構</td>
      </tr>
      <tr>
          <td>有足夠的時間完成驗證？</td>
          <td>可以繼續</td>
          <td>記錄後排入計畫</td>
      </tr>
  </tbody>
</table>
<p>三個問題都回答「是」，才開始動手。</p>
<h2 id="wave-分解策略">Wave 分解策略</h2>
<p>大規模重構最容易失敗的原因是：試圖一次做完所有事情。</p>
<blockquote>
<p><strong>Wave</strong>：一個有明確目標和驗證點的重構階段。每完成一個 Wave，程式碼都必須處於可用狀態。</p></blockquote>
<p>Wave 分解的核心思想是：<strong>把重構拆成多個有序的 Wave，確保每一步都可驗證、可回退。</strong></p>
<h3 id="v0280基礎架構重構">v0.28.0：基礎架構重構</h3>
<p>v0.28.0 將 Hook 系統從「各自為政」重構為「共用模組 + 配置分離」的架構。拆分為 4 個 Wave：</p>
<h4 id="wave-1建立共用程式庫">Wave 1：建立共用程式庫</h4>
<p>先建立共用模組的介面和測試，不改動任何現有 Hook。</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">config_loader    4 個測試
</span></span><span class="line"><span class="ln">3</span><span class="cl">git_utils        6 個測試
</span></span><span class="line"><span class="ln">4</span><span class="cl">hook_io          3 個測試
</span></span><span class="line"><span class="ln">5</span><span class="cl">hook_logging     2 個測試</span></span></code></pre></div><p>為什麼先做這步？因為後續 Wave 需要依賴這些模組。如果跳過這步直接改 Hook，會遇到「要用的函式還不存在」的問題。</p>
<h4 id="wave-2配置分離">Wave 2：配置分離</h4>
<p>把 task-dispatch 中的硬編碼清單（代理人名稱、品質規則、指令對應表）抽到 YAML 配置檔。這是行數最多、修改範圍最大的階段。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 重構前：硬編碼在 Python 中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">AGENTS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s2">&#34;parsley-flutter-developer&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;implementation&#34;</span><span class="p">,</span> <span class="s2">&#34;lang&#34;</span><span class="p">:</span> <span class="s2">&#34;dart&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="s2">&#34;thyme-python-developer&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;implementation&#34;</span><span class="p">,</span> <span class="s2">&#34;lang&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="c1"># ... 15 個代理人定義散落在程式碼中</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 重構後：讀取 YAML 配置</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="n">agents</span> <span class="o">=</span> <span class="n">config_loader</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents.yaml&#34;</span><span class="p">)</span></span></span></code></pre></div><p>分離後，新增代理人只需要編輯 YAML 檔案，不需要動 Python 程式碼。</p>
<h4 id="wave-3逐檔重構">Wave 3：逐檔重構</h4>
<p>有了共用程式庫和配置檔，逐一修改 Hook 檔案。每改完一個檔案就執行測試，確保沒改壞東西。這個階段的關鍵是<strong>紀律</strong>：每次只改一個檔案，改完就跑測試，不要累積多個修改後一起驗證。</p>
<h4 id="wave-4驗證與清理">Wave 4：驗證與清理</h4>
<p>28 個單元測試全部通過。移除不再需要的重複程式碼。檢查是否有遺漏的相依性。</p>
<h3 id="v0310風格統一與防護強化">v0.31.0：風格統一與防護強化</h3>
<p>v0.31.0 的重構是在既有架構上統一風格和強化防護，規模與性質都與 v0.28.0 不同。拆分為 4 個 Wave（W22-W25）：</p>
<table>
  <thead>
      <tr>
          <th>Wave</th>
          <th>目標</th>
          <th>性質</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>W22</td>
          <td>日誌系統統一</td>
          <td>機械性修改</td>
      </tr>
      <tr>
          <td>W23</td>
          <td>訊息常數抽取</td>
          <td>機械性修改</td>
      </tr>
      <tr>
          <td>W24</td>
          <td>程式碼風格統一</td>
          <td>機械性，但觸發了作用域問題</td>
      </tr>
      <tr>
          <td>W25</td>
          <td>修復 W24 問題 + 防護機制</td>
          <td>修復 + 新功能</td>
      </tr>
  </tbody>
</table>
<p>注意 W25 的存在。它不在原始計畫中，而是 W24 出問題後臨時新增的。<strong>好的分解策略要預留處理意外的空間。</strong></p>
<p>兩次重構的對比：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>v0.28.0</th>
          <th>v0.31.0</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>性質</td>
          <td>建立新架構</td>
          <td>統一既有架構的風格</td>
      </tr>
      <tr>
          <td>風險來源</td>
          <td>介面設計是否正確</td>
          <td>機械性修改是否有隱含的邏輯變更</td>
      </tr>
      <tr>
          <td>Wave 數</td>
          <td>4（全部計畫內）</td>
          <td>4（第 4 個是意外新增）</td>
      </tr>
      <tr>
          <td>教訓</td>
          <td>先建基礎設施再動手</td>
          <td>機械性修改也可能觸發邏輯問題</td>
      </tr>
  </tbody>
</table>
<h3 id="wave-分解的原則">Wave 分解的原則</h3>
<p>從兩次重構經驗中，可以歸納出三個分解原則：</p>
<h4 id="原則-1依賴方向決定順序">原則 1：依賴方向決定順序</h4>
<p>被依賴的模組先做。v0.28.0 先建共用程式庫（Wave 1），因為後續所有 Wave 都會用到它。</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">Wave 1: 共用模組（被所有 Hook 依賴）
</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">Wave 2: 配置檔（被 task-dispatch 依賴）
</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">Wave 3: Hook 檔案（依賴共用模組和配置檔）
</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">Wave 4: 驗證（依賴所有修改完成）</span></span></code></pre></div><h4 id="原則-2機械性修改和邏輯修改分開">原則 2：機械性修改和邏輯修改分開</h4>
<p>機械性修改（如統一命名風格、統一匯入路徑）和邏輯修改（如改變函式行為、改變變數作用域）不該放在同一個 Wave。</p>
<p>v0.31.0 的 W24 表面上是機械性修改（統一 logger 初始化位置），但實際上涉及了作用域的邏輯變更——把全域變數移入函式，會影響所有引用它的其他函式。如果在規劃時就識別出這一點，應該把「移動 logger 位置」和「修改函式簽名以傳遞 logger 參數」拆成兩個步驟。</p>
<p>怎麼區分？問自己：<strong>這個修改會不會改變任何函式的可見變數？</strong> 如果會，就不是純機械性修改。</p>
<h4 id="原則-3每個-wave-結束時程式碼必須可用">原則 3：每個 Wave 結束時程式碼必須可用</h4>
<p>不能出現「改到一半，程式跑不起來」的狀態。每個 Wave 完成後：</p>
<ul>
<li>所有測試通過</li>
<li>程式碼可正常執行</li>
<li>可以安全地提交</li>
</ul>
<p>這保證了即使中途需要停下來處理其他事情，程式碼也不會處於損壞狀態。</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">Step 1: 畫出依賴關係圖
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    → 哪些模組被其他模組依賴？（先做）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    → 哪些模組互相獨立？（可以並行）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">Step 2: 分類修改類型
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    → 機械性修改：統一命名、統一格式、統一匯入
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    → 邏輯修改：改變函式行為、改變資料流、改變作用域
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">Step 3: 分配到 Wave
</span></span><span class="line"><span class="ln">10</span><span class="cl">    → Wave N: 基礎設施（被依賴的、獨立的）
</span></span><span class="line"><span class="ln">11</span><span class="cl">    → Wave N+1: 機械性修改（依賴基礎設施）
</span></span><span class="line"><span class="ln">12</span><span class="cl">    → Wave N+2: 邏輯修改（依賴前面的修改）
</span></span><span class="line"><span class="ln">13</span><span class="cl">    → Wave N+3: 驗證與清理
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">Step 4: 每個 Wave 定義驗證標準
</span></span><span class="line"><span class="ln">16</span><span class="cl">    → 哪些測試必須通過？
</span></span><span class="line"><span class="ln">17</span><span class="cl">    → 程式碼能正常執行嗎？
</span></span><span class="line"><span class="ln">18</span><span class="cl">    → 可以安全提交嗎？</span></span></code></pre></div><h2 id="度量表">度量表</h2>
<p>用量化指標驗證重構是否達到目標：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>v0.28.0 前</th>
          <th>v0.28.0 後</th>
          <th>v0.31.0 後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>共用模組數</td>
          <td>0</td>
          <td>4</td>
          <td>7+</td>
      </tr>
      <tr>
          <td>重複程式碼行數</td>
          <td>~415 行</td>
          <td>~0</td>
          <td>~0</td>
      </tr>
      <tr>
          <td>新增 Hook 的樣板行數</td>
          <td>~15 行</td>
          <td>~10 行</td>
          <td>~5 行（核心呼叫 1 行）</td>
      </tr>
      <tr>
          <td>Error Patterns 記錄數</td>
          <td>4</td>
          <td>8</td>
          <td>19</td>
      </tr>
      <tr>
          <td>task-dispatch 行數</td>
          <td>858</td>
          <td>296</td>
          <td>267</td>
      </tr>
  </tbody>
</table>
<p>幾個值得注意的趨勢：</p>
<ul>
<li><strong>共用模組數</strong>從 0 成長到 7+。這代表重複程式碼有了歸屬，不再散落各處。</li>
<li><strong>新增 Hook 的樣板行數</strong>從 15 行降到約 5 行（核心只需 1 行匯入呼叫）。新增一個 Hook 從「複製一堆工具函式」變成「匯入共用模組」。</li>
<li><strong>Error Patterns</strong> 從 4 成長到 19。這不是壞事——它代表團隊開始系統性地記錄和傳承經驗，而不是每個人各自出問題。</li>
</ul>
<h2 id="小結">小結</h2>
<p>重構的決策可以歸納為三個問題：</p>
<ol>
<li><strong>該不該做？</strong> 認知負擔超載、重複程式碼累積、維護成本持續上升，就該做。</li>
<li><strong>現在能做嗎？</strong> 有測試保護、不在修 Bug、時間充裕，才能做。</li>
<li><strong>怎麼拆分？</strong> 按依賴順序，機械和邏輯分開，每步結束都可用。</li>
</ol>
<p>後續章節會深入每個具體的重構技巧：如何識別壞味道、如何抽取配置、如何消除重複、如何處理作用域陷阱。</p>
<h2 id="思考題">思考題</h2>
<ol>
<li>
<p>你目前的專案中，有哪些檔案的行數超過 200 行？列出前三名，分析它們為什麼會這麼長——是職責太多、重複程式碼、還是配置和邏輯混在一起？</p>
</li>
<li>
<p>回想一次你在修 Bug 時順手重構的經驗。事後回頭看 <code>git log</code>，能清楚區分哪些變更是修 Bug、哪些是重構嗎？</p>
</li>
<li>
<p>如果你的專案完全沒有測試，但認知負擔已經很高，你會怎麼規劃「補測試」和「重構」的先後順序？</p>
</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>
<p>用以下指令掃描你的專案，找出重複定義的函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在專案根目錄執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">grep -h <span class="s2">&#34;^def &#34;</span> your_project/*.py <span class="p">|</span> sort <span class="p">|</span> uniq -c <span class="p">|</span> sort -rn <span class="p">|</span> head -10</span></span></code></pre></div><p>分析結果：哪些函式被重複定義了？它們應該被抽到哪個共用模組？</p>
</li>
<li>
<p>選一個超過 200 行的檔案，嘗試畫出它的 Wave 分解計畫。回答以下問題：</p>
<ul>
<li>哪些部分被其他部分依賴？（先做）</li>
<li>哪些修改是機械性的？（可以批量處理）</li>
<li>每個 Wave 完成後，程式碼能正常執行嗎？</li>
</ul>
</li>
<li>
<p>計算你選定檔案的認知負擔指數（變數數 + 分支數 + 巢狀深度 + 依賴數）。找出指數最高的函式，思考如何將它拆分到指數低於 10。</p>
</li>
</ol>
<hr>
<p>下一章：<a href="/blog/python/07-refactoring/code-smells/" data-link-title="程式碼壞味道偵測" data-link-desc="從三級分類系統到偵測工具鏈，建立系統化的程式碼品質防線">程式碼壞味道偵測</a></p>
<p><em>文件版本：v0.31.0</em>
<em>最後更新：2026-03-04</em></p>
]]></content:encoded></item><item><title>非程式碼的重構</title><link>https://tarrragon.github.io/blog/python/07-refactoring/non-code-refactoring/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/07-refactoring/non-code-refactoring/</guid><description>&lt;p>前面幾章我們重構的對象都是程式碼：提取函式、消除魔法數字、分離配置。但你有沒有想過，&lt;strong>文件也會腐敗&lt;/strong>？&lt;/p>
&lt;p>當一份規則文件從 50 行長到 450 行，閱讀者要在腦中同時追蹤的概念數量跟&lt;a href="https://tarrragon.github.io/blog/python/07-refactoring/refactoring-strategy/" data-link-title="重構的動機與策略" data-link-desc="從 Hook 系統重構經驗出發，學習何時重構、何時不該重構，以及如何將大規模重構拆分成可管理的階段">第一章&lt;/a>提到的那個 858 行 Python 檔案沒有本質區別。認知負擔不只存在於程式碼中——任何需要人類閱讀和理解的東西都受它影響。&lt;/p>
&lt;h2 id="問題文件膨脹">問題：文件膨脹&lt;/h2>
&lt;p>v0.28.0 到 v0.31.0 之間，專案的規則文件經歷了 9 個版本的迭代。每次迭代都在解決真實的問題：補充遺漏的邊界情況、新增流程步驟、記錄決策理由。每一次修改都合理，但累積的結果是：&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">parallel-dispatch.md 280 行 ← 原本是「什麼時候可以並行」的簡單指南
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">task-splitting.md 230 行 ← 原本是「怎麼拆任務」的清單
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">ticket-lifecycle.md 220 行 ← 原本是「Ticket 狀態怎麼轉」的流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">version-progression.md 180 行 ← 原本是「什麼時候推進版本」的判斷
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">incident-response.md 170 行 ← 原本是「出錯了怎麼辦」的流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">query-vs-research.md 130 行 ← 原本是「查資料要不要派人」的二選一
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">plan-to-ticket.md 100 行 ← 原本是「計畫怎麼變成 Ticket」的流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">decision-tree.md 452 行 ← 核心決策樹，每次都在「補一個分支」&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題不是內容不正確——每一行都有存在的理由。問題是&lt;strong>讀不動&lt;/strong>。&lt;/p>
&lt;p>一個新加入的代理人需要閱讀 &lt;code>parallel-dispatch.md&lt;/code> 來決定能不能並行派發任務。它真正需要的資訊是：觸發條件（5 行）、安全檢查清單（6 行）、決策流程圖（5 行）。但它必須在 280 行中找到這些——剩下的 264 行是 5W1H 格式範例、分析任務的並行原則、Agent Teams 場景表、進度追蹤模板。&lt;/p>
&lt;p>這就像在一個 500 行的函式裡找那 20 行核心邏輯。&lt;/p>
&lt;h3 id="膨脹的過程">膨脹的過程&lt;/h3>
&lt;p>文件膨脹的方式和程式碼膨脹幾乎一模一樣。回顧 &lt;code>parallel-dispatch.md&lt;/code> 的成長軌跡：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>版本&lt;/th>
 &lt;th>行數&lt;/th>
 &lt;th>新增原因&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>v1.0&lt;/td>
 &lt;td>60 行&lt;/td>
 &lt;td>初始版本：觸發條件 + 決策流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>v1.1&lt;/td>
 &lt;td>90 行&lt;/td>
 &lt;td>補充：安全檢查清單（因為有人忘記檢查檔案衝突）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>v1.2&lt;/td>
 &lt;td>130 行&lt;/td>
 &lt;td>新增：Agent Teams 派發方式（新功能）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>v2.0&lt;/td>
 &lt;td>180 行&lt;/td>
 &lt;td>新增：5W1H 格式範例（因為有人不知道怎麼寫）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>v2.3&lt;/td>
 &lt;td>230 行&lt;/td>
 &lt;td>新增：分析任務並行原則、進度追蹤模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>v2.5&lt;/td>
 &lt;td>280 行&lt;/td>
 &lt;td>新增：並行派發後驗證流程（因為有人忘記驗證）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每一次新增都在解決真實問題。沒有人故意讓文件變長。但六個版本之後，一個簡單的「能不能並行」判斷指南變成了包羅萬象的操作手冊。&lt;/p>
&lt;p>這和程式碼中的「上帝函式」成因一樣：每次「加一點」都合理，但沒有人停下來問「這個函式是不是該拆了」。&lt;/p>
&lt;h2 id="解決方案progressive-disclosure">解決方案：Progressive Disclosure&lt;/h2>
&lt;p>程式碼重構有 Extract Method——把函式內部的細節提取到獨立函式中。文件重構有對應的技巧：&lt;strong>Progressive Disclosure&lt;/strong>（漸進式揭露）。&lt;/p>
&lt;p>核心思想：&lt;strong>常駐只保留決策入口和強制規則，細節放到參考文件中按需載入。&lt;/strong>&lt;/p>
&lt;p>這和函式設計的道理一樣。你不會把排序演算法的完整實作放在 &lt;code>main()&lt;/code> 裡面，你會呼叫 &lt;code>sort()&lt;/code>。同樣地，規則文件的讀者不需要在判斷「能不能並行」時看到「Agent Teams 的 3-4x 成本計算方式」。&lt;/p>
&lt;h3 id="精簡原則">精簡原則&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>單一責任&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>DRY&lt;/td>
 &lt;td>細節只寫一次，放在 references/&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;code>parallel-dispatch.md&lt;/code> 為例，精簡過程分三步：&lt;/p>
&lt;h4 id="step-1識別決策入口和參考細節">Step 1：識別「決策入口」和「參考細節」&lt;/h4>
&lt;p>閱讀 280 行內容，對每一段問：「讀者在做『能不能並行』這個決策時需要這段嗎？」&lt;/p>
&lt;ul>
&lt;li>觸發條件表格 → 需要（決策入口）&lt;/li>
&lt;li>安全檢查清單 → 需要（強制規則）&lt;/li>
&lt;li>決策流程圖 → 需要（快查表）&lt;/li>
&lt;li>數量原則 → 需要（簡短規則）&lt;/li>
&lt;li>不適用場景 → 需要（負面清單）&lt;/li>
&lt;li>5W1H 格式範例 → 不需要（移出）&lt;/li>
&lt;li>Agent Teams 場景表 → 不需要（移出）&lt;/li>
&lt;li>進度追蹤模板 → 不需要（移出）&lt;/li>
&lt;/ul>
&lt;h4 id="step-2提取到參考文件">Step 2：提取到參考文件&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl"># 精簡前
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">parallel-dispatch.md (280 行，所有內容混在一起)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"># 精簡後
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">parallel-dispatch.md (98 行，決策入口 + 強制規則)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> └→ references/parallel-dispatch-details.md (剩餘細節，按需查閱)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="step-3加入連結">Step 3：加入連結&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="gu">## 並行派發後驗證（強制）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">所有並行代理人回報完成後，**必須**執行 &lt;span class="sb">`git diff --stat`&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">&amp;gt; 詳細驗證步驟和常見原因：.claude/references/parallel-dispatch-details.md&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>讀者看到這段就知道：「驗證是強制的，具體步驟在那個連結裡。」它可以選擇深入閱讀，也可以先完成手邊的決策。&lt;/p></description><content:encoded><![CDATA[<p>前面幾章我們重構的對象都是程式碼：提取函式、消除魔法數字、分離配置。但你有沒有想過，<strong>文件也會腐敗</strong>？</p>
<p>當一份規則文件從 50 行長到 450 行，閱讀者要在腦中同時追蹤的概念數量跟<a href="/blog/python/07-refactoring/refactoring-strategy/" data-link-title="重構的動機與策略" data-link-desc="從 Hook 系統重構經驗出發，學習何時重構、何時不該重構，以及如何將大規模重構拆分成可管理的階段">第一章</a>提到的那個 858 行 Python 檔案沒有本質區別。認知負擔不只存在於程式碼中——任何需要人類閱讀和理解的東西都受它影響。</p>
<h2 id="問題文件膨脹">問題：文件膨脹</h2>
<p>v0.28.0 到 v0.31.0 之間，專案的規則文件經歷了 9 個版本的迭代。每次迭代都在解決真實的問題：補充遺漏的邊界情況、新增流程步驟、記錄決策理由。每一次修改都合理，但累積的結果是：</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">parallel-dispatch.md     280 行  ← 原本是「什麼時候可以並行」的簡單指南
</span></span><span class="line"><span class="ln">2</span><span class="cl">task-splitting.md        230 行  ← 原本是「怎麼拆任務」的清單
</span></span><span class="line"><span class="ln">3</span><span class="cl">ticket-lifecycle.md      220 行  ← 原本是「Ticket 狀態怎麼轉」的流程
</span></span><span class="line"><span class="ln">4</span><span class="cl">version-progression.md   180 行  ← 原本是「什麼時候推進版本」的判斷
</span></span><span class="line"><span class="ln">5</span><span class="cl">incident-response.md     170 行  ← 原本是「出錯了怎麼辦」的流程
</span></span><span class="line"><span class="ln">6</span><span class="cl">query-vs-research.md     130 行  ← 原本是「查資料要不要派人」的二選一
</span></span><span class="line"><span class="ln">7</span><span class="cl">plan-to-ticket.md        100 行  ← 原本是「計畫怎麼變成 Ticket」的流程
</span></span><span class="line"><span class="ln">8</span><span class="cl">decision-tree.md         452 行  ← 核心決策樹，每次都在「補一個分支」</span></span></code></pre></div><p>問題不是內容不正確——每一行都有存在的理由。問題是<strong>讀不動</strong>。</p>
<p>一個新加入的代理人需要閱讀 <code>parallel-dispatch.md</code> 來決定能不能並行派發任務。它真正需要的資訊是：觸發條件（5 行）、安全檢查清單（6 行）、決策流程圖（5 行）。但它必須在 280 行中找到這些——剩下的 264 行是 5W1H 格式範例、分析任務的並行原則、Agent Teams 場景表、進度追蹤模板。</p>
<p>這就像在一個 500 行的函式裡找那 20 行核心邏輯。</p>
<h3 id="膨脹的過程">膨脹的過程</h3>
<p>文件膨脹的方式和程式碼膨脹幾乎一模一樣。回顧 <code>parallel-dispatch.md</code> 的成長軌跡：</p>
<table>
  <thead>
      <tr>
          <th>版本</th>
          <th>行數</th>
          <th>新增原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>v1.0</td>
          <td>60 行</td>
          <td>初始版本：觸發條件 + 決策流程</td>
      </tr>
      <tr>
          <td>v1.1</td>
          <td>90 行</td>
          <td>補充：安全檢查清單（因為有人忘記檢查檔案衝突）</td>
      </tr>
      <tr>
          <td>v1.2</td>
          <td>130 行</td>
          <td>新增：Agent Teams 派發方式（新功能）</td>
      </tr>
      <tr>
          <td>v2.0</td>
          <td>180 行</td>
          <td>新增：5W1H 格式範例（因為有人不知道怎麼寫）</td>
      </tr>
      <tr>
          <td>v2.3</td>
          <td>230 行</td>
          <td>新增：分析任務並行原則、進度追蹤模板</td>
      </tr>
      <tr>
          <td>v2.5</td>
          <td>280 行</td>
          <td>新增：並行派發後驗證流程（因為有人忘記驗證）</td>
      </tr>
  </tbody>
</table>
<p>每一次新增都在解決真實問題。沒有人故意讓文件變長。但六個版本之後，一個簡單的「能不能並行」判斷指南變成了包羅萬象的操作手冊。</p>
<p>這和程式碼中的「上帝函式」成因一樣：每次「加一點」都合理，但沒有人停下來問「這個函式是不是該拆了」。</p>
<h2 id="解決方案progressive-disclosure">解決方案：Progressive Disclosure</h2>
<p>程式碼重構有 Extract Method——把函式內部的細節提取到獨立函式中。文件重構有對應的技巧：<strong>Progressive Disclosure</strong>（漸進式揭露）。</p>
<p>核心思想：<strong>常駐只保留決策入口和強制規則，細節放到參考文件中按需載入。</strong></p>
<p>這和函式設計的道理一樣。你不會把排序演算法的完整實作放在 <code>main()</code> 裡面，你會呼叫 <code>sort()</code>。同樣地，規則文件的讀者不需要在判斷「能不能並行」時看到「Agent Teams 的 3-4x 成本計算方式」。</p>
<h3 id="精簡原則">精簡原則</h3>
<p>我們制定了四條原則來指導文件重構，和程式碼重構的原則一一對應：</p>
<table>
  <thead>
      <tr>
          <th>程式碼原則</th>
          <th>文件原則</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單一責任</td>
          <td>一個文件回答一個問題</td>
          <td>常駐文件只回答「怎麼判斷」，不回答「細節怎麼做」</td>
      </tr>
      <tr>
          <td>資訊層級</td>
          <td>決策入口 → 強制規則 → 參考細節</td>
          <td>讀者按需深入，不強制閱讀全部</td>
      </tr>
      <tr>
          <td>DRY</td>
          <td>細節只寫一次，放在 references/</td>
          <td>多個文件引用同一份參考，不複製貼上</td>
      </tr>
      <tr>
          <td>量化驗證</td>
          <td>精簡前後行數對比</td>
          <td>有數字才知道改善了多少</td>
      </tr>
  </tbody>
</table>
<h3 id="具體做法">具體做法</h3>
<p>以 <code>parallel-dispatch.md</code> 為例，精簡過程分三步：</p>
<h4 id="step-1識別決策入口和參考細節">Step 1：識別「決策入口」和「參考細節」</h4>
<p>閱讀 280 行內容，對每一段問：「讀者在做『能不能並行』這個決策時需要這段嗎？」</p>
<ul>
<li>觸發條件表格 → 需要（決策入口）</li>
<li>安全檢查清單 → 需要（強制規則）</li>
<li>決策流程圖 → 需要（快查表）</li>
<li>數量原則 → 需要（簡短規則）</li>
<li>不適用場景 → 需要（負面清單）</li>
<li>5W1H 格式範例 → 不需要（移出）</li>
<li>Agent Teams 場景表 → 不需要（移出）</li>
<li>進度追蹤模板 → 不需要（移出）</li>
</ul>
<h4 id="step-2提取到參考文件">Step 2：提取到參考文件</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># 精簡前
</span></span><span class="line"><span class="ln">2</span><span class="cl">parallel-dispatch.md (280 行，所有內容混在一起)
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"># 精簡後
</span></span><span class="line"><span class="ln">5</span><span class="cl">parallel-dispatch.md (98 行，決策入口 + 強制規則)
</span></span><span class="line"><span class="ln">6</span><span class="cl">  └→ references/parallel-dispatch-details.md (剩餘細節，按需查閱)</span></span></code></pre></div><h4 id="step-3加入連結">Step 3：加入連結</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gu">## 並行派發後驗證（強制）
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">所有並行代理人回報完成後，**必須**執行 <span class="sb">`git diff --stat`</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">&gt; 詳細驗證步驟和常見原因：.claude/references/parallel-dispatch-details.md</span></span></code></pre></div><p>讀者看到這段就知道：「驗證是強制的，具體步驟在那個連結裡。」它可以選擇深入閱讀，也可以先完成手邊的決策。</p>
<h3 id="精簡前後對比">精簡前後對比</h3>
<p>看看 <code>incident-response.md</code> 精簡前後的結構差異：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># 精簡前 (170 行)
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 強制流程
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></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="gu">## 強制觸發條件
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></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="gu">## 派發對應表
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></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="gu">## 多視角分析原則             ← Level 3
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></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="gu">## 安全等級分類               ← Level 3
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></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="gu">## 報告格式範例               ← Level 3
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="gu"></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="gu">## 禁止行為
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="gu"></span>（禁止清單）</span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gh"># 精簡後 (64 行)
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="gu">## 強制流程
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="gu"></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="gu">## 強制觸發條件
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="gu"></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="gu">## 派發對應表
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="gu"></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="gu">## 禁止行為
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="gu"></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="gu">## 相關文件
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="gu"></span>&gt; 詳細規則：.claude/references/incident-response-details.md</span></span></code></pre></div><p>Level 3 的內容（多視角分析、安全等級、報告格式）整段移到 <code>references/</code> 目錄。常駐文件只剩下做決策需要的資訊。</p>
<h2 id="實際成果">實際成果</h2>
<p>7 個規則文件的精簡結果：</p>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>精簡前</th>
          <th>精簡後</th>
          <th>縮減</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>parallel-dispatch.md</td>
          <td>280 行</td>
          <td>98 行</td>
          <td>-65%</td>
      </tr>
      <tr>
          <td>task-splitting.md</td>
          <td>230 行</td>
          <td>93 行</td>
          <td>-60%</td>
      </tr>
      <tr>
          <td>ticket-lifecycle.md</td>
          <td>220 行</td>
          <td>70 行</td>
          <td>-68%</td>
      </tr>
      <tr>
          <td>version-progression.md</td>
          <td>180 行</td>
          <td>69 行</td>
          <td>-62%</td>
      </tr>
      <tr>
          <td>incident-response.md</td>
          <td>170 行</td>
          <td>64 行</td>
          <td>-62%</td>
      </tr>
      <tr>
          <td>query-vs-research.md</td>
          <td>130 行</td>
          <td>46 行</td>
          <td>-65%</td>
      </tr>
      <tr>
          <td>plan-to-ticket.md</td>
          <td>100 行</td>
          <td>43 行</td>
          <td>-57%</td>
      </tr>
      <tr>
          <td><strong>合計</strong></td>
          <td><strong>1310 行</strong></td>
          <td><strong>483 行</strong></td>
          <td><strong>-63%</strong></td>
      </tr>
  </tbody>
</table>
<p>核心決策樹 <code>decision-tree.md</code> 也從 452 行精簡到 286 行（-37%）。它的縮減幅度較小，因為決策樹本身就是「決策入口」——大部分內容都是必要的分支判斷。</p>
<blockquote>
<p><strong>後記</strong>：上面的數字是 v3.0.0 精簡完成時的快照。精簡後不到兩週，<code>plan-to-ticket.md</code> 因為新增「執行中額外發現」流程從 43 行長回 87 行，<code>decision-tree.md</code> 因為新增 TDD Phase 路由從 286 行長回 434 行。這不代表精簡失敗——新增的內容都是必要的新功能。但它提醒我們：<strong>文件重構和程式碼重構一樣，是持續的紀律。</strong></p></blockquote>
<h2 id="通用原則">通用原則</h2>
<p>從這次文件重構中，我們提煉出四個可複用的原則：</p>
<h3 id="原則-1單一責任">原則 1：單一責任</h3>
<p>每份文件應該只回答一個核心問題。</p>
<table>
  <thead>
      <tr>
          <th>文件</th>
          <th>核心問題</th>
          <th>不該包含</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>parallel-dispatch.md</td>
          <td>能不能並行？</td>
          <td>Agent Teams 的完整操作手冊</td>
      </tr>
      <tr>
          <td>incident-response.md</td>
          <td>出錯了怎麼處理？</td>
          <td>每種錯誤的詳細分析範例</td>
      </tr>
      <tr>
          <td>ticket-lifecycle.md</td>
          <td>Ticket 狀態怎麼轉？</td>
          <td>Hook 的技術實作細節</td>
      </tr>
  </tbody>
</table>
<p>判斷方式和函式一樣：如果描述這份文件的用途需要「和」這個字，它可能需要拆分。</p>
<h3 id="原則-2資訊層級">原則 2：資訊層級</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">Level 0: 快查表（一眼就能找到答案）
</span></span><span class="line"><span class="ln">2</span><span class="cl">Level 1: 強制規則（必須遵守的約束）
</span></span><span class="line"><span class="ln">3</span><span class="cl">Level 2: 決策流程（判斷邏輯）
</span></span><span class="line"><span class="ln">4</span><span class="cl">Level 3: 參考細節（範例、模板、歷史記錄）</span></span></code></pre></div><p>常駐文件只包含 Level 0-2，Level 3 放在 <code>references/</code> 目錄下。</p>
<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"># Level 0: 函式簽名（一眼就知道做什麼）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">can_dispatch_parallel</span><span class="p">(</span><span class="n">tasks</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Task</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"> 3</span><span class="cl">    <span class="c1"># Level 1: Guard clauses（強制規則）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">tasks</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">2</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="kc">False</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">if</span> <span class="n">has_dependency</span><span class="p">(</span><span class="n">tasks</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></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"># Level 2: 核心邏輯（判斷邏輯）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="ow">not</span> <span class="n">has_file_overlap</span><span class="p">(</span><span class="n">tasks</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"># Level 3: 實作細節（在被呼叫的函式裡）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">has_file_overlap</span><span class="p">(</span><span class="n">tasks</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 50 行的具體實作...</span></span></span></code></pre></div><p>同一個概念，同一個結構。</p>
<h3 id="原則-3dry">原則 3：DRY</h3>
<p>文件之間也會出現重複。同一段「Wave 獨立性原則」如果在 <code>parallel-dispatch.md</code>、<code>task-splitting.md</code>、<code>version-progression.md</code> 三個地方都寫了，修改時就要改三處。</p>
<p>解決方式和程式碼一樣：抽到共用的參考文件，其他文件用連結引用。</p>
<h3 id="原則-4量化驗證">原則 4：量化驗證</h3>
<p>沒有數字的重構是自我感覺良好。精簡前後一定要量化比較：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 文件行數統計</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">wc -l .claude/rules/flows/*.md .claude/rules/guides/*.md
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 前後對比</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;精簡前: 1310 行&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;精簡後:  483 行&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;縮減率: 63%&#34;</span></span></span></code></pre></div><p>和程式碼重構的 <code>-65%</code> 行數對比一樣，行數本身不是目標，但它是認知負擔降低的代理指標。</p>
<h2 id="常見錯誤">常見錯誤</h2>
<p>文件重構也有自己的陷阱：</p>
<h3 id="錯誤-1過度精簡">錯誤 1：過度精簡</h3>
<p>把所有細節都移走，常駐文件只剩下標題和連結。讀者每做一個決策都要點開參考文件，跳轉次數太多反而增加認知負擔。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln">1</span><span class="cl"><span class="gh"># 錯誤：過度精簡
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="gh"></span><span class="gu">## 並行派發
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="gu"></span><span class="k">&gt; </span><span class="ge">詳見：references/parallel-dispatch-details.md
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="ge"></span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="gu">## 任務拆分
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="gu"></span>&gt; 詳見：references/task-splitting-details.md</span></span></code></pre></div><p>這等於一個函式裡全是 <code>call_other_function()</code>，讀者什麼都看不到。常駐文件至少要包含決策入口（判斷邏輯）和強制規則（不可違反的約束）。</p>
<h3 id="錯誤-2按章節拆分而非按層級拆分">錯誤 2：按「章節」拆分而非按「層級」拆分</h3>
<p>把文件按目錄拆成多個小文件，但每個小文件仍然混合了決策入口和參考細節。這只是把一個大問題變成了多個小問題。</p>
<p>正確的拆分維度是<strong>資訊層級</strong>，不是<strong>主題章節</strong>。</p>
<h3 id="錯誤-3沒有更新連結">錯誤 3：沒有更新連結</h3>
<p>精簡後忘記在常駐文件中加入參考文件的連結。讀者需要細節時找不到入口。這和重構後忘記更新 import 一樣危險。</p>
<h2 id="程式碼-vs-文件重構對照表">程式碼 vs 文件重構對照表</h2>
<p>程式碼重構和文件重構的手法其實是同一套思維的不同實踐：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>程式碼重構</th>
          <th>文件重構</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>壞味道</td>
          <td>函式太長、巢狀過深</td>
          <td>文件太長、層級不分</td>
      </tr>
      <tr>
          <td>核心手法</td>
          <td>Extract Method</td>
          <td>Progressive Disclosure</td>
      </tr>
      <tr>
          <td>單一責任</td>
          <td>一個函式做一件事</td>
          <td>一份文件回答一個問題</td>
      </tr>
      <tr>
          <td>DRY</td>
          <td>提取共用模組</td>
          <td>提取共用參考文件</td>
      </tr>
      <tr>
          <td>量化指標</td>
          <td>行數、認知負擔指數</td>
          <td>行數、常駐 vs 參考比例</td>
      </tr>
      <tr>
          <td>驗證方式</td>
          <td>測試通過</td>
          <td>讀者能在 30 秒內找到答案</td>
      </tr>
      <tr>
          <td>失敗的重構</td>
          <td>過度拆分導致跳轉太多</td>
          <td>過度精簡導致資訊不足</td>
      </tr>
      <tr>
          <td>觸發條件</td>
          <td>函式超過 30 行</td>
          <td>文件超過 100 行且混合多個層級</td>
      </tr>
      <tr>
          <td>典型比例</td>
          <td>858 行 → 296 行 (-65%)</td>
          <td>280 行 → 98 行 (-65%)</td>
      </tr>
  </tbody>
</table>
<p>最後一行不是巧合。兩個案例的縮減率接近，是因為底層原理相同：大約 1/3 的內容是核心邏輯（決策入口），2/3 的內容是支撐細節（參考資料）。</p>
<h2 id="什麼時候該重構文件">什麼時候該重構文件</h2>
<p>和程式碼一樣，不是所有文件都需要重構。觸發條件：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>閾值</th>
          <th>行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>文件長度</td>
          <td>&gt; 100 行且混合多層級</td>
          <td>評估是否需要分離</td>
      </tr>
      <tr>
          <td>讀者反饋</td>
          <td>「找不到需要的資訊」</td>
          <td>重新組織資訊層級</td>
      </tr>
      <tr>
          <td>更新頻率</td>
          <td>每次更新都在不同段落</td>
          <td>考慮按變更頻率拆分</td>
      </tr>
      <tr>
          <td>重複內容</td>
          <td>同一段話出現在 2+ 份文件</td>
          <td>提取到共用參考</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li>你的專案有沒有「什麼都寫在 README」的情況？如果有，試著用 Progressive Disclosure 原則拆分它。</li>
<li>為什麼 <code>decision-tree.md</code> 的縮減幅度（-37%）比其他文件（-57% 到 -68%）小？這說明了什麼？</li>
<li>文件重構有一個程式碼重構沒有的風險：「過度精簡導致讀者找不到需要的資訊」。你會怎麼驗證精簡後的文件仍然足夠完整？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<h3 id="練習-1評估文件健康度">練習 1：評估文件健康度</h3>
<p>找一份超過 150 行的文件（你自己的專案 README、API 文件、團隊 Wiki），回答以下問題：</p>
<ul>
<li>這份文件回答幾個核心問題？（如果超過 1 個，考慮拆分）</li>
<li>讀者能在 30 秒內找到最常需要的資訊嗎？</li>
<li>有沒有段落只在特定情境下才需要閱讀？</li>
</ul>
<h3 id="練習-2執行-progressive-disclosure">練習 2：執行 Progressive Disclosure</h3>
<p>對那份文件執行三步精簡：</p>
<ol>
<li>標記每一段的資訊層級（Level 0-3）</li>
<li>將 Level 3 的內容提取到獨立的參考文件</li>
<li>在原文件中加入連結</li>
</ol>
<h3 id="練習-3量化驗證">練習 3：量化驗證</h3>
<p>計算精簡前後的行數和縮減率。如果縮減率低於 30%，思考是否原本的結構就已經不錯。如果超過 70%，檢查是否過度精簡了。</p>
<details>
<summary>參考範圍</summary>
<p>根據本章的實際案例，健康的縮減率大約在 55%-68% 之間。核心決策類文件（如 decision-tree）的縮減率通常較低（30%-40%），因為它本身就是決策入口。</p>
</details>
<h2 id="小結">小結</h2>
<p>重構不只是程式碼的事。任何需要人類閱讀的東西——規則文件、操作手冊、架構文件——都會隨著時間膨脹，累積認知負擔。</p>
<p>核心手法是 Progressive Disclosure：常駐只保留讀者<strong>當下需要</strong>的資訊，細節放到參考文件中<strong>按需載入</strong>。這和 Extract Method 的道理完全一樣：呼叫端只需要知道函式名稱和參數，不需要看到完整實作。</p>
<p>程式碼壞味道有 code smell，文件壞味道也有——只是比較少人談論。</p>
<hr>
<p><em>上一章：<a href="/blog/python/07-refactoring/refactoring-pitfalls/" data-link-title="重構陷阱與防護" data-link-desc="三個真實重構事故的共通模式：部分更新問題與系統性防護方法">重構陷阱與防護</a></em>
<em>下一章：<a href="/blog/python/07-refactoring/case-study/" data-link-title="完整案例回顧" data-link-desc="從超過 30 個 Hook 各自為政到系統化品質工程，三個階段的完整重構復盤">完整案例回顧</a></em></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>