<?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/tags/%E7%A8%8B%E5%BC%8F%E5%93%81%E8%B3%AA/</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>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E7%A8%8B%E5%BC%8F%E5%93%81%E8%B3%AA/index.xml" rel="self" type="application/rss+xml"/><item><title>程式碼自然語言化撰寫方法論</title><link>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%87%AA%E7%84%B6%E8%AA%9E%E8%A8%80%E5%8C%96%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/%E7%A8%8B%E5%BC%8F%E7%A2%BC%E8%87%AA%E7%84%B6%E8%AA%9E%E8%A8%80%E5%8C%96%E6%92%B0%E5%AF%AB%E6%96%B9%E6%B3%95%E8%AB%96/</guid><description>&lt;p>程式碼不是寫給電腦看的，是寫給人類讀的。電腦只管執行，人類才要維護。&lt;/p></description><content:encoded><![CDATA[<p>程式碼不是寫給電腦看的，是寫給人類讀的。電腦只管執行，人類才要維護。</p>
<h2 id="認知負擔一切的出發點">認知負擔：一切的出發點</h2>
<p>人類工作記憶有限，大約一次只能處理七個項目（Miller&rsquo;s Law）。看到縮寫要在腦中展開、看到模糊詞要猜測含義、看到長函式要分段記憶——這些都是認知負擔。</p>
<p>自然語言化的目標很簡單：讓程式碼像讀文章一樣自然，把讀者的認知資源留給理解業務邏輯，而不是解碼程式碼本身。</p>
<hr>
<h2 id="第一原則命名要能直接讀懂">第一原則：命名要能直接讀懂</h2>
<h3 id="函式命名">函式命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：不知道在做什麼
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kt">void</span> <span class="n">handle</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1">// 正確：一眼看懂
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">calculateBookReadingProgress</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="kt">void</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">User</span> <span class="n">user</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kt">void</span> <span class="n">enrichBookMetadataFromExternalSource</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{}</span></span></span></code></pre></div><h3 id="變數命名">變數命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：縮寫和多用途
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="n">usr</span> <span class="o">=</span> <span class="n">getCurrentUser</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">var</span> <span class="n">data</span> <span class="o">=</span> <span class="n">loadUserData</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">processBookData</span><span class="p">();</span> <span class="c1">// 100行後同一變數換了意思
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">// 正確：明確且專用
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">authenticatedUser</span> <span class="o">=</span> <span class="n">getCurrentUser</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kd">final</span> <span class="n">userProfileData</span> <span class="o">=</span> <span class="n">loadUserData</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="kd">final</span> <span class="n">enrichedBookMetadata</span> <span class="o">=</span> <span class="n">processBookData</span><span class="p">();</span></span></span></code></pre></div><h3 id="類別命名">類別命名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 錯誤：說不清在做什麼
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kd">class</span> <span class="nc">Manager</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">class</span> <span class="nc">Handler</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">class</span> <span class="nc">BookDAO</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="c1"></span><span class="kd">class</span> <span class="nc">BookMetadataEnrichmentService</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="kd">class</span> <span class="nc">UserRegistrationValidator</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="kd">class</span> <span class="nc">LibraryBookSearchEngine</span> <span class="p">{}</span></span></span></code></pre></div><h3 id="布林命名">布林命名</h3>
<p>布林變數應該能讀成問句，在 if 裡就能自然被理解：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kt">bool</span> <span class="n">isValid</span><span class="p">;</span>      <span class="c1">// &#34;Is valid?&#34;
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">hasPermission</span><span class="p">;</span> <span class="c1">// &#34;Has permission?&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="kt">bool</span> <span class="n">canEdit</span><span class="p">;</span>       <span class="c1">// &#34;Can edit?&#34;
</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 class="kt">bool</span> <span class="n">permission</span><span class="p">;</span></span></span></code></pre></div><h3 id="常見命名反模式">常見命名反模式</h3>
<ul>
<li><strong>匈牙利命名法</strong>：<code>strName</code>, <code>intCount</code> — 型別系統自己會提供型別資訊，名稱不用重複</li>
<li><strong>無意義前綴</strong>：<code>theUser</code>, <code>aBook</code> — 沒帶來任何資訊，直接刪掉</li>
<li><strong>過度縮寫</strong>：<code>usrMgr</code> — 迫使讀者展開，<code>userManager</code> 更自然</li>
<li><strong>數字後綴</strong>：<code>user1</code>, <code>user2</code> — 改成 <code>primaryUser</code>, <code>secondaryUser</code> 才說明關係</li>
</ul>
<hr>
<h2 id="第二原則函式控制在五到十行">第二原則：函式控制在五到十行</h2>
<p>超過十行通常表示函式承擔了多重職責。判斷是否需要拆分的最快方法：函式名稱裡有「和」或「或」的話，一定要拆。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：15行，三種職責混在一起
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">Book</span> <span class="n">processBook</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</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="p">(</span><span class="n">isbn</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;Invalid ISBN&#39;</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="p">(</span><span class="o">!</span><span class="n">isValidISBNChecksum</span><span class="p">(</span><span class="n">isbn</span><span class="p">))</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN checksum failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="s1">&#39;/books/</span><span class="si">$</span><span class="n">isbn</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">statusCode</span> <span class="o">!=</span> <span class="m">200</span><span class="p">)</span> <span class="k">throw</span> <span class="n">Exception</span><span class="p">(</span><span class="s1">&#39;API failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="kd">final</span> <span class="n">bookData</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">body</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nl">id:</span> <span class="n">BookId</span><span class="p">(</span><span class="n">generateUniqueId</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nl">title:</span> <span class="n">BookTitle</span><span class="p">(</span><span class="n">bookData</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">source</span><span class="o">:</span> <span class="n">BookSource</span><span class="p">.</span><span class="n">external</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span 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">// 正確：拆成三個單一職責函式
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="n">Book</span> <span class="n">createBookFromISBN</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="n">validateISBNFormat</span><span class="p">(</span><span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="kd">final</span> <span class="n">bookData</span> <span class="o">=</span> <span class="n">fetchBookDataFromExternalAPI</span><span class="p">(</span><span class="n">isbn</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">return</span> <span class="n">buildBookFromExternalData</span><span class="p">(</span><span class="n">bookData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kt">void</span> <span class="n">validateISBNFormat</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">isbn</span><span class="p">.</span><span class="n">length</span> <span class="o">!=</span> <span class="m">13</span><span class="p">)</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN must be 13 digits&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidISBNChecksum</span><span class="p">(</span><span class="n">isbn</span><span class="p">))</span> <span class="k">throw</span> <span class="n">ArgumentError</span><span class="p">(</span><span class="s1">&#39;ISBN checksum validation failed&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">dynamic</span><span class="o">&gt;</span> <span class="n">fetchBookDataFromExternalAPI</span><span class="p">(</span><span class="kt">String</span> <span class="n">isbn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="s1">&#39;/books/</span><span class="si">$</span><span class="n">isbn</span><span class="s1">&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">statusCode</span> <span class="o">!=</span> <span class="m">200</span><span class="p">)</span> <span class="k">throw</span> <span class="n">Exception</span><span class="p">(</span><span class="s1">&#39;Failed to fetch book data&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">return</span> <span class="n">json</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">apiResponse</span><span class="p">.</span><span class="n">body</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="n">Book</span> <span class="n">buildBookFromExternalData</span><span class="p">(</span><span class="n">Map</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">dynamic</span><span class="o">&gt;</span> <span class="n">bookData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">  <span class="k">return</span> <span class="n">Book</span><span class="p">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nl">id:</span> <span class="n">BookId</span><span class="p">(</span><span class="n">generateUniqueId</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nl">title:</span> <span class="n">BookTitle</span><span class="p">(</span><span class="n">bookData</span><span class="p">[</span><span class="s1">&#39;title&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">source</span><span class="o">:</span> <span class="n">BookSource</span><span class="p">.</span><span class="n">external</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第三原則一個變數只做一件事">第三原則：一個變數只做一件事</h2>
<p>同一個變數在不同地方承載不同意義，是我見過最難追蹤的 bug 來源之一。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：同一變數三種身分
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="n">result</span> <span class="o">=</span> <span class="n">validateUser</span><span class="p">(</span><span class="n">userData</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="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</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">processPayment</span><span class="p">(</span><span class="n">paymentData</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="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">success</span><span class="p">)</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">updateDatabase</span><span class="p">(</span><span class="n">result</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 class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// 正確：每個變數都有自己的名字
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">userValidationResult</span> <span class="o">=</span> <span class="n">validateUser</span><span class="p">(</span><span class="n">userData</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="p">(</span><span class="n">userValidationResult</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="kd">final</span> <span class="n">paymentProcessingResult</span> <span class="o">=</span> <span class="n">processPayment</span><span class="p">(</span><span class="n">paymentData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">paymentProcessingResult</span><span class="p">.</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="kd">final</span> <span class="n">databaseUpdateResult</span> <span class="o">=</span> <span class="n">updateDatabase</span><span class="p">(</span><span class="n">paymentProcessingResult</span><span class="p">.</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>變數生命週期也要管：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：books 在100行間一直換狀態
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">processLibraryBooks</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="kd">var</span> <span class="n">books</span> <span class="o">=</span> <span class="n">getAllBooks</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">books</span> <span class="o">=</span> <span class="n">filterAvailableBooks</span><span class="p">(</span><span class="n">books</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="n">books</span> <span class="o">=</span> <span class="n">sortBooksByTitle</span><span class="p">(</span><span class="n">books</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 正確：每個階段的狀態都有名字
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">processLibraryBooks</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="kd">final</span> <span class="n">allLibraryBooks</span> <span class="o">=</span> <span class="n">getAllBooks</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="kd">final</span> <span class="n">availableBooks</span> <span class="o">=</span> <span class="n">filterAvailableBooks</span><span class="p">(</span><span class="n">allLibraryBooks</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kd">final</span> <span class="n">sortedAvailableBooks</span> <span class="o">=</span> <span class="n">sortBooksByTitle</span><span class="p">(</span><span class="n">availableBooks</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第四原則用事件驅動表達業務流程">第四原則：用事件驅動表達業務流程</h2>
<p>複雜的業務流程往往會寫成一個大函式，裡面塞滿 if/else。問題不在於 if/else 本身，而是把不同職責的邏輯混在一起。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：驗證、API呼叫、結果處理全混在一起
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">submitForm</span><span class="p">(</span><span class="n">FormData</span> <span class="n">formData</span><span class="p">)</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="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;姓名不能為空&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</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="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">)</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;Email不能為空&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</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="p">(</span><span class="o">!</span><span class="n">isValidEmail</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">))</span> <span class="p">{</span> <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;Email格式不正確&#39;</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="kd">final</span> <span class="n">apiResult</span> <span class="o">=</span> <span class="n">submitToAPI</span><span class="p">(</span><span class="n">formData</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="p">(</span><span class="n">apiResult</span><span class="p">.</span><span class="n">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">showSuccessMessage</span><span class="p">(</span><span class="s1">&#39;提交成功&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">navigateToSuccessPage</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">clearForm</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">showErrorMessage</span><span class="p">(</span><span class="s1">&#39;提交失敗：&#39;</span> <span class="o">+</span> <span class="n">apiResult</span><span class="p">.</span><span class="n">error</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">highlightErrorFields</span><span class="p">(</span><span class="n">apiResult</span><span class="p">.</span><span class="n">errorFields</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// 正確：每個事件有自己的函式
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">submitUserRegistrationForm</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="kd">final</span> <span class="n">validationResult</span> <span class="o">=</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">validationResult</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">handleSuccessfulValidation</span><span class="p">(</span><span class="n">formData</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">handleValidationFailure</span><span class="p">(</span><span class="n">validationResult</span><span class="p">.</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">ValidationResult</span> <span class="n">validateUserRegistrationData</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="kd">final</span> <span class="n">errors</span> <span class="o">=</span> <span class="o">&lt;</span><span class="n">ValidationError</span><span class="o">&gt;</span><span class="p">[];</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isValidUserName</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">name</span><span class="p">))</span> <span class="n">errors</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">ValidationError</span><span class="p">.</span><span class="n">invalidUserName</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="p">(</span><span class="o">!</span><span class="n">isValidUserEmail</span><span class="p">(</span><span class="n">formData</span><span class="p">.</span><span class="n">email</span><span class="p">))</span> <span class="n">errors</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">ValidationError</span><span class="p">.</span><span class="n">invalidEmail</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">.</span><span class="n">fromErrors</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="kt">void</span> <span class="n">handleSuccessfulValidation</span><span class="p">(</span><span class="n">UserRegistrationFormData</span> <span class="n">formData</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">  <span class="n">submitUserRegistrationToAPI</span><span class="p">(</span><span class="n">formData</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">.</span><span class="n">then</span><span class="p">(</span><span class="n">handleSuccessfulAPIResponse</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">.</span><span class="n">catchError</span><span class="p">(</span><span class="n">handleAPIFailure</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="kt">void</span> <span class="n">handleValidationFailure</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">ValidationError</span><span class="o">&gt;</span> <span class="n">errors</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">  <span class="n">displayValidationErrors</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">  <span class="n">highlightInvalidFormFields</span><span class="p">(</span><span class="n">errors</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>狀態機也是同樣的道理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：一個函式根據狀態做完全不同的事
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">updateBookStatus</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">,</span> <span class="kt">String</span> <span class="n">newStatus</span><span class="p">)</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="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;available&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">available</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">borrower</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">updateSearchIndex</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">notifyWaitingUsers</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;borrowed&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">borrowed</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">borrowDate</span> <span class="o">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">now</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">sendBorrowConfirmation</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">newStatus</span> <span class="o">==</span> <span class="s1">&#39;maintenance&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">maintenance</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">removeFromSearchIndex</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">notifyMaintenanceTeam</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">// 正確：每個事件獨立
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span><span class="kt">void</span> <span class="n">handleBookReturnEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="n">executeBookReturn</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="n">notifyBookBecameAvailable</span><span class="p">(</span><span class="n">book</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="kt">void</span> <span class="n">handleBookBorrowEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">,</span> <span class="n">User</span> <span class="n">borrower</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="n">executeBookBorrow</span><span class="p">(</span><span class="n">book</span><span class="p">,</span> <span class="n">borrower</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="n">confirmBorrowingToUser</span><span class="p">(</span><span class="n">book</span><span class="p">,</span> <span class="n">borrower</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="kt">void</span> <span class="n">handleBookMaintenanceEvent</span><span class="p">(</span><span class="n">Book</span> <span class="n">book</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="n">markBookForMaintenance</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">  <span class="n">notifyMaintenanceRequired</span><span class="p">(</span><span class="n">book</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="第五原則可讀性優於簡潔性">第五原則：可讀性優於簡潔性</h2>
<p>程式碼的價值排序：正確性 &gt; 可讀性 &gt; 可維護性 &gt; 簡潔性。</p>
<p>行數從來不是指標，清晰才是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 錯誤：為了少寫幾行犧牲可讀性
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">books</span><span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">b</span><span class="p">.</span><span class="n">s</span> <span class="o">==</span> <span class="s1">&#39;a&#39;</span> <span class="o">&amp;&amp;</span> <span class="n">b</span><span class="p">.</span><span class="n">p</span> <span class="o">&gt;</span> <span class="m">100</span><span class="p">).</span><span class="n">map</span><span class="p">((</span><span class="n">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">b</span><span class="p">.</span><span class="n">t</span><span class="p">).</span><span class="n">toList</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1">// 正確：每一步都說清楚在做什麼
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="kd">final</span> <span class="n">availableBooksWithMoreThan100Pages</span> <span class="o">=</span> <span class="n">allBooks</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">BookStatus</span><span class="p">.</span><span class="n">available</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">.</span><span class="n">where</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">pageCount</span> <span class="o">&gt;</span> <span class="m">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">.</span><span class="n">toList</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="kd">final</span> <span class="n">bookTitlesForDisplay</span> <span class="o">=</span> <span class="n">availableBooksWithMoreThan100Pages</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">.</span><span class="n">map</span><span class="p">((</span><span class="n">book</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="n">book</span><span class="p">.</span><span class="n">title</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">.</span><span class="n">toList</span><span class="p">();</span></span></span></code></pre></div><hr>
<h2 id="如何驗證程式碼品質">如何驗證程式碼品質</h2>
<p><strong>陌生人測試</strong>：讓不熟悉這段程式碼的工程師讀。5分鐘內能理解主要邏輯算合格，需要解釋才能理解就要重寫。</p>
<p><strong>自然語言測試</strong>：把程式碼翻譯成中文說出來。翻譯流暢自然算合格，說不清楚就改命名。</p>
<p><strong>六個月後測試</strong>：假設半年後的自己要修改這段程式碼，能快速找到位置算合格，不敢動怕壞掉就要重新設計。</p>
<hr>
<p>每一行程式碼都是一句話，每個函式都是一個段落。好的程式碼是對未來維護者的體貼——不只是風格偏好，而是降低維護成本的工程決策。</p>]]></content:encoded></item></channel></rss>