<?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>模組零：設計哲學（序章） on Tarragon</title><link>https://tarrragon.github.io/blog/python/00-philosophy/</link><description>Recent content in 模組零：設計哲學（序章） on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 20 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/python/00-philosophy/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/00-philosophy/cost-thinking/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/00-philosophy/cost-thinking/</guid><description>&lt;h2 id="什麼是軟體開發的成本">什麼是軟體開發的成本？&lt;/h2>
&lt;p>當我們談論軟體開發的「成本」，大多數人想到的是開發時間：「這個功能需要多少工時？」&lt;/p>
&lt;p>但這只是冰山一角。&lt;/p>
&lt;h3 id="顯性成本-vs-隱性成本">顯性成本 vs 隱性成本&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>成本類型&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;th>容易被看見？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開發時間&lt;/td>
 &lt;td>寫程式碼、除錯&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護成本&lt;/td>
 &lt;td>修改 11 處重複程式碼&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復成本&lt;/td>
 &lt;td>自訂實作引入 bug 後的 hotfix&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗成本&lt;/td>
 &lt;td>任務失敗後的重試和浪費&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施債務&lt;/td>
 &lt;td>缺乏可觀測性導致的除錯時間&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計決策的長期代價&lt;/td>
 &lt;td>選擇了不適當的清理頻率&lt;/td>
 &lt;td>否&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>隱性成本的特點是：決策當下看不見，但會在未來反覆出現。&lt;/p>
&lt;h3 id="成本思維的核心問題">成本思維的核心問題&lt;/h3>
&lt;p>每次做技術決策時，問自己：&lt;/p>
&lt;blockquote>
&lt;p>這個決策的「總成本」是多少？不只是現在的開發成本，還包括未來的維護、修復、擴展成本。&lt;/p>&lt;/blockquote>
&lt;p>這就是成本思維的本質：&lt;strong>把時間軸拉長來評估決策。&lt;/strong>&lt;/p>
&lt;h2 id="重新造輪子的真實成本">重新造輪子的真實成本&lt;/h2>
&lt;h3 id="一個看似合理的決策">一個看似合理的決策&lt;/h3>
&lt;p>假設你需要一個「延遲建立檔案」的日誌 Handler &amp;ndash; 只有在真正寫入日誌時才建立檔案，避免產生空的日誌檔。&lt;/p>
&lt;p>你可能會這樣想：「標準庫的 FileHandler 不支援延遲建立，我自己寫一個。」&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 自訂實作（看似合理，實則隱藏成本）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">LazyFileHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FileHandler&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;延遲建立檔案的 Handler&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">filename&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 不呼叫 super().__init__() 以避免建立檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Handler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">record&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">makedirs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">exist_ok&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_file_created&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">emit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">record&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1"># AttributeError: &amp;#39;LazyFileHandler&amp;#39; has no attribute &amp;#39;stream&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="隱藏的成本鏈">隱藏的成本鏈&lt;/h3>
&lt;p>這段程式碼引發了一連串的成本：&lt;/p>





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





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 一行解決&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">logging&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FileHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delay&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="c1"># delay=True：延遲到第一次 emit 時才建立檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 3.0 就已存在，經過 15+ 年的穩定性驗證&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>開發成本：約 1 分鐘。維護成本：零。修復成本：零。&lt;/p>
&lt;h3 id="成本對比">成本對比&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>自訂 LazyFileHandler&lt;/th>
 &lt;th>標準庫 delay=True&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開發時間&lt;/td>
 &lt;td>30 分鐘&lt;/td>
 &lt;td>1 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式碼行數&lt;/td>
 &lt;td>20+ 行&lt;/td>
 &lt;td>1 行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>測試需求&lt;/td>
 &lt;td>需要自行測試&lt;/td>
 &lt;td>標準庫已驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bug 風險&lt;/td>
 &lt;td>高（跳過 super 初始化）&lt;/td>
 &lt;td>極低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護成本&lt;/td>
 &lt;td>需要持續維護&lt;/td>
 &lt;td>零&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>總成本&lt;/td>
 &lt;td>~4 小時&lt;/td>
 &lt;td>~1 分鐘&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>教訓：在寫任何自訂實作之前，先花 5 分鐘搜尋標準庫。這 5 分鐘的投資，可能節省數小時的維護和除錯成本。&lt;/p>
&lt;h2 id="重複程式碼的累積成本">重複程式碼的累積成本&lt;/h2>
&lt;h3 id="從-1-處到-11-處">從 1 處到 11 處&lt;/h3>
&lt;p>一個簡單的函式，從 stdin 讀取 JSON：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 這段程式碼出現在 11 個 Hook 檔案中&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">read_json_from_stdin&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>當它只出現在 1 個檔案中時，問題不大。但隨著 Hook 數量增加，這段程式碼被複製到了 11 個檔案。&lt;/p>
&lt;h3 id="累積成本的計算">累積成本的計算&lt;/h3>
&lt;p>假設有一天你需要修改這個函式的行為（例如加入錯誤日誌記錄）：&lt;/p>





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





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 自訂實作（看似合理，實則隱藏成本）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">LazyFileHandler</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;延遲建立檔案的 Handler&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">&#39;a&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">filename</span> <span class="o">=</span> <span class="n">filename</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">mode</span> <span class="o">=</span> <span class="n">mode</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_file_created</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="c1"># 不呼叫 super().__init__() 以避免建立檔案</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">logging</span><span class="o">.</span><span class="n">Handler</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">emit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_file_created</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">filename</span><span class="p">),</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_file_created</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># AttributeError: &#39;LazyFileHandler&#39; has no attribute &#39;stream&#39;</span></span></span></code></pre></div><h3 id="隱藏的成本鏈">隱藏的成本鏈</h3>
<p>這段程式碼引發了一連串的成本：</p>





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





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 一行解決</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">delay</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># delay=True：延遲到第一次 emit 時才建立檔案</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Python 3.0 就已存在，經過 15+ 年的穩定性驗證</span></span></span></code></pre></div><p>開發成本：約 1 分鐘。維護成本：零。修復成本：零。</p>
<h3 id="成本對比">成本對比</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>自訂 LazyFileHandler</th>
          <th>標準庫 delay=True</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發時間</td>
          <td>30 分鐘</td>
          <td>1 分鐘</td>
      </tr>
      <tr>
          <td>程式碼行數</td>
          <td>20+ 行</td>
          <td>1 行</td>
      </tr>
      <tr>
          <td>測試需求</td>
          <td>需要自行測試</td>
          <td>標準庫已驗證</td>
      </tr>
      <tr>
          <td>Bug 風險</td>
          <td>高（跳過 super 初始化）</td>
          <td>極低</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>需要持續維護</td>
          <td>零</td>
      </tr>
      <tr>
          <td>總成本</td>
          <td>~4 小時</td>
          <td>~1 分鐘</td>
      </tr>
  </tbody>
</table>
<p>教訓：在寫任何自訂實作之前，先花 5 分鐘搜尋標準庫。這 5 分鐘的投資，可能節省數小時的維護和除錯成本。</p>
<h2 id="重複程式碼的累積成本">重複程式碼的累積成本</h2>
<h3 id="從-1-處到-11-處">從 1 處到 11 處</h3>
<p>一個簡單的函式，從 stdin 讀取 JSON：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這段程式碼出現在 11 個 Hook 檔案中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">read_json_from_stdin</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><p>當它只出現在 1 個檔案中時，問題不大。但隨著 Hook 數量增加，這段程式碼被複製到了 11 個檔案。</p>
<h3 id="累積成本的計算">累積成本的計算</h3>
<p>假設有一天你需要修改這個函式的行為（例如加入錯誤日誌記錄）：</p>





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





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





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




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





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





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 發現問題            0 分鐘（偶然發現，否則可能更久）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 確認哪些 Hook 失敗    30 分鐘（需要手動逐一檢查）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 找到失敗原因          2 小時（沒有日誌可看，只能猜測）
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 修復失敗的 Hook       1 小時
</span></span><span class="line"><span class="ln">5</span><span class="cl">5. 驗證修復效果          30 分鐘
</span></span><span class="line"><span class="ln">6</span><span class="cl">6. 確認沒有其他受影響的部分  1 小時
</span></span><span class="line"><span class="ln">7</span><span class="cl">─────────────────────────
</span></span><span class="line"><span class="ln">8</span><span class="cl">   總成本：~5 小時（且可能仍有遺漏）</span></span></code></pre></div><h3 id="有可觀測性的除錯成本">有可觀測性的除錯成本</h3>
<p>如果一開始就投資可觀測性基礎設施：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook_safely</span><span class="p">(</span><span class="n">hook_func</span><span class="p">,</span> <span class="n">hook_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="n">hook_func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="c1"># 寫入檔案日誌（完整追蹤）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">log_to_file</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Hook 失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">traceback</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="c1"># 輸出到 stderr（確保使用者可見）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[Hook Error] </span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span></span></span></code></pre></div><p>除錯過程變成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 發現問題           0 分鐘（stderr 立即可見）
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 確認失敗原因        5 分鐘（日誌有完整的 traceback）
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 修復失敗的 Hook     30 分鐘
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 驗證修復效果        10 分鐘
</span></span><span class="line"><span class="ln">5</span><span class="cl">─────────────────────────
</span></span><span class="line"><span class="ln">6</span><span class="cl">   總成本：~45 分鐘</span></span></code></pre></div><h3 id="投資回報分析">投資回報分析</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>無可觀測性</th>
          <th>有可觀測性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>前期投資</td>
          <td>0 小時</td>
          <td>~8 小時（建設日誌架構）</td>
      </tr>
      <tr>
          <td>每次除錯</td>
          <td>~5 小時</td>
          <td>~45 分鐘</td>
      </tr>
      <tr>
          <td>3 次事故後總成本</td>
          <td>15 小時</td>
          <td>8 + 2.25 = 10.25 小時</td>
      </tr>
      <tr>
          <td>5 次事故後總成本</td>
          <td>25 小時</td>
          <td>8 + 3.75 = 11.75 小時</td>
      </tr>
      <tr>
          <td>問題發現延遲</td>
          <td>數小時到數天</td>
          <td>即時</td>
      </tr>
  </tbody>
</table>
<p>只要遇到 3 次以上的事故，可觀測性投資就開始回本。而在任何有一定規模的系統中，問題出現 3 次幾乎是必然的。</p>
<p>教訓：可觀測性是「看不見的基礎設施」。它的缺失不會直接造成 bug，但會讓每個 bug 的修復成本倍增。</p>
<h2 id="系統設計中的頻率取捨">系統設計中的頻率取捨</h2>
<h3 id="問題背景">問題背景</h3>
<p>一個 Hook 系統每次執行都會產生日誌檔案。隨著時間累積，過期的日誌需要被清理。問題是：<strong>多久清理一次？</strong></p>
<h3 id="三種方案的成本比較">三種方案的成本比較</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 方案 A：每次都清理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">execute_hook_logic</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cleanup_old_logs</span><span class="p">()</span>  <span class="c1"># 每次 Hook 執行後都清理</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 方案 B：每 N 次清理一次</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">run_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">execute_hook_logic</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">state</span><span class="p">[</span><span class="s2">&#34;execution_count&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">state</span><span class="p">[</span><span class="s2">&#34;execution_count&#34;</span><span class="p">]</span> <span class="o">%</span> <span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">cleanup_old_logs</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 方案 C：外部排程清理</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 由 cron job 或系統排程器負責</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># Hook 本身不做任何清理</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>方案 A：每次清理</th>
          <th>方案 B：每 N 次</th>
          <th>方案 C：外部排程</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>I/O 成本</td>
          <td>高（每次都掃描目錄）</td>
          <td>低（每 10 次一次）</td>
          <td>零（Hook 無關）</td>
      </tr>
      <tr>
          <td>精確度</td>
          <td>高（即時清理）</td>
          <td>中（最多延遲 10 次）</td>
          <td>高（可設定精確排程）</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>低</td>
          <td>中（需要計數器）</td>
          <td>高（需要外部依賴）</td>
      </tr>
      <tr>
          <td>對 Hook 效能影響</td>
          <td>有（每次增加 I/O）</td>
          <td>小</td>
          <td>無</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>低</td>
          <td>低</td>
          <td>中（需維護排程設定）</td>
      </tr>
  </tbody>
</table>
<h3 id="決策依據找到平衡點">決策依據：找到平衡點</h3>
<p>方案 B 被選中，原因是：</p>
<ol>
<li><strong>I/O 成本可控</strong> &ndash; 每 10 次才觸發一次，對效能影響極小</li>
<li><strong>精確度可接受</strong> &ndash; 日誌多存留幾次不是關鍵問題</li>
<li><strong>零外部依賴</strong> &ndash; 不需要額外的 cron 配置</li>
<li><strong>實作簡單</strong> &ndash; 一個計數器加一個 if 判斷</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">maybe_cleanup_logs</span><span class="p">(</span><span class="n">execution_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">log_dir</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    根據執行次數決定是否清理舊日誌。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    每 LOG_CLEANUP_TRIGGER_FREQUENCY 次觸發一次清理，
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    在精確度和 I/O 成本之間取得平衡。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="n">execution_count</span> <span class="o">%</span> <span class="n">LOG_CLEANUP_TRIGGER_FREQUENCY</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">cleanup_old_logs</span><span class="p">(</span><span class="n">log_dir</span><span class="p">)</span></span></span></code></pre></div><p>教訓：「最佳方案」不存在，只有「在當前限制條件下成本最低的方案」。頻率問題的本質是精確度和成本之間的取捨。</p>
<h2 id="失敗的成本">失敗的成本</h2>
<h3 id="預驗證-vs-失敗重試">預驗證 vs 失敗重試</h3>
<p>在派發任務之前，有一個關鍵的成本決策：<strong>是否先驗證任務的可行性？</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 方案 A：直接執行，失敗再處理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">dispatch_task</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">execute</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>  <span class="c1"># 消耗資源</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">except</span> <span class="ne">PermissionError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 失敗了，資源已經浪費</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">log</span><span class="p">(</span><span class="s2">&#34;任務失敗：權限不足&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 方案 B：預先驗證</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">dispatch_task</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">has_required_permissions</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">log</span><span class="p">(</span><span class="s2">&#34;跳過：權限不足&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">execute</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>  <span class="c1"># 確認可行才消耗資源</span></span></span></code></pre></div><h3 id="真實場景">真實場景</h3>
<p>兩個探索任務被派發去存取跨專案的資源，但都因為權限限制而失敗。每個任務各消耗了大量運算資源，但結果為零 &ndash; 完全浪費。</p>
<p>如果在派發前花 1 分鐘確認權限，就能避免這些浪費。</p>
<h3 id="預驗證的成本公式">預驗證的成本公式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">預驗證成本 = 驗證時間 x 每次派發
</span></span><span class="line"><span class="ln">2</span><span class="cl">失敗成本 = 任務執行時間 x 失敗機率
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">當 失敗成本 &gt; 預驗證成本 時，預驗證是值得的</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>場景</th>
          <th>預驗證成本</th>
          <th>失敗成本</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>快速本地操作</td>
          <td>高（相對於操作本身）</td>
          <td>低</td>
          <td>不需預驗證</td>
      </tr>
      <tr>
          <td>耗時遠端操作</td>
          <td>低（相對於操作本身）</td>
          <td>高</td>
          <td>必須預驗證</td>
      </tr>
      <tr>
          <td>高失敗率操作</td>
          <td>低</td>
          <td>高</td>
          <td>必須預驗證</td>
      </tr>
      <tr>
          <td>低失敗率操作</td>
          <td>中</td>
          <td>低</td>
          <td>視情況而定</td>
      </tr>
  </tbody>
</table>
<p>教訓：失敗不是免費的。每次失敗都消耗了資源、時間和注意力。預驗證是一種「用小成本避免大浪費」的投資。</p>
<h2 id="歸納成本思維的核心原則">歸納：成本思維的核心原則</h2>
<h3 id="原則一計算總成本不只是開發成本">原則一：計算總成本，不只是開發成本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">總成本 = 開發成本 + 維護成本 + 修復成本 + 機會成本</span></span></code></pre></div><p>一個「快速完成」的方案，如果未來每次修改都要花 3 倍時間，那它其實是最昂貴的方案。</p>
<h3 id="原則二重複的成本會指數增長">原則二：重複的成本會指數增長</h3>
<p>每一份重複的程式碼都是一顆定時炸彈。它的爆炸威力隨著修改次數和時間而增長。</p>
<h3 id="原則三先搜尋再建造">原則三：先搜尋再建造</h3>
<p>在寫任何自訂實作之前，先花 5 分鐘搜尋：</p>
<ul>
<li>標準庫有沒有這個功能？</li>
<li>專案中有沒有類似的實作？</li>
<li>有沒有經過驗證的第三方方案？</li>
</ul>
<p>這 5 分鐘的搜尋成本，遠低於自訂實作可能帶來的維護成本。</p>
<h3 id="原則四可觀測性是必要投資">原則四：可觀測性是必要投資</h3>
<p>看不見的問題成本最高。因為：</p>
<ul>
<li>你不知道它存在（發現成本高）</li>
<li>你不知道它影響多大（評估成本高）</li>
<li>你不知道它什麼時候開始的（追溯成本高）</li>
</ul>
<h3 id="原則五找到取捨的平衡點">原則五：找到取捨的平衡點</h3>
<p>很少有決策是「A 絕對比 B 好」。更多的情況是：</p>
<blockquote>
<p>A 在維度 X 上更好，B 在維度 Y 上更好。</p></blockquote>
<p>成本思維是在限制條件下找到<strong>總成本最低的方案</strong>。</p>
<h3 id="原則六失敗有成本預防是投資">原則六：失敗有成本，預防是投資</h3>
<p>每次失敗都消耗資源。適當的預驗證和防護措施是一種投資 &ndash; 用確定的小成本，避免不確定的大損失。</p>
<h2 id="自我檢查清單">自我檢查清單</h2>
<p>做技術決策時，問自己這些問題：</p>
<ul>
<li><input disabled="" type="checkbox"> 這個方案的維護成本是多少？（不只是開發成本）</li>
<li><input disabled="" type="checkbox"> 標準庫或現有程式碼中有沒有類似的解決方案？</li>
<li><input disabled="" type="checkbox"> 這段程式碼會被複製到其他地方嗎？（DRY 風險）</li>
<li><input disabled="" type="checkbox"> 如果這裡出了問題，我能多快發現？（可觀測性）</li>
<li><input disabled="" type="checkbox"> 這個任務失敗的成本是多少？需要預驗證嗎？</li>
<li><input disabled="" type="checkbox"> 頻率設計是否在精確度和成本之間取得平衡？</li>
</ul>
<h2 id="小結">小結</h2>
<p>成本思維是把時間軸拉長來做決策。</p>
<p>很多「快速」的決策，在長期看來是最昂貴的。而很多看似「多餘」的投資（可觀測性、共用模組、預驗證），在長期看來反而是成本最低的選擇。</p>
<p>軟體開發不只是寫程式碼 &ndash; 它是在有限資源下做出無數個取捨決策。理解每個決策的隱性成本，才能做出真正「划算」的選擇。</p>
<blockquote>
<p>最便宜的 bug 是那個從未被寫出來的 bug。</p></blockquote>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python/00-philosophy/cognitive-load/" data-link-title="認知負擔：程式碼設計的核心目的" data-link-desc="所有設計原則的統一視角：降低閱讀者的認知負擔">認知負擔：程式碼設計的核心目的</a> - 認知負擔也是一種「隱性成本」</li>
<li><a href="/blog/python/00-philosophy/naming-art/" data-link-title="命名的藝術：讓程式碼說故事" data-link-desc="透過命名降低認知負擔，讓程式碼像故事一樣易讀">命名的藝術：讓程式碼說故事</a> - 好的命名降低閱讀成本</li>
<li><a href="/blog/python/00-philosophy/open-closed-principle/" data-link-title="開放封閉原則與認知負擔" data-link-desc="從認知負擔的視角重新理解 SOLID 原則">開放封閉原則與認知負擔</a> - OCP 降低擴展成本</li>
<li><a href="/blog/python/07-refactoring/dry-principle/" data-link-title="DRY 原則與共用程式庫" data-link-desc="學習識別重複程式碼並建立共用模組，含模組演進與漸進遷移策略">DRY 原則與共用程式庫</a> - 重複程式碼的成本控制實戰</li>
<li><a href="/blog/python/05-error-testing/observability-design/" data-link-title="5.6 Hook 系統可觀測性設計" data-link-desc="日誌架構、錯誤可見性、健康監控：讓 44 個 Hook 的運行狀態透明可追蹤">Hook 系統可觀測性設計</a> - 可觀測性投資的詳細案例</li>
</ul>
<hr>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>McConnell, S. (2004). &ldquo;Code Complete: A Practical Handbook of Software Construction&rdquo;</li>
<li>Forsgren, N., Humble, J., &amp; Kim, G. (2018). &ldquo;Accelerate: The Science of Lean Software and DevOps&rdquo;</li>
</ul>
]]></content:encoded></item></channel></rss>